Setup
library(ggplot2) # Load ggplot2
## Warning: package 'ggplot2' was built under R version 4.3.1
library(ggdark) # Load ggdark
## Warning: package 'ggdark' was built under R version 4.3.1
library(ggimage) # Load ggimage
## Warning: package 'ggimage' was built under R version 4.3.1
library(reshape) # Load reshape
## Warning: package 'reshape' was built under R version 4.3.1
library(ggridges) # Load ggridges
## Warning: package 'ggridges' was built under R version 4.3.1
library(ggrepel) # Load ggrepel
## Warning: package 'ggrepel' was built under R version 4.3.1
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.3.1
## Warning: package 'tibble' was built under R version 4.3.1
## Warning: package 'tidyr' was built under R version 4.3.1
## Warning: package 'readr' was built under R version 4.3.1
## Warning: package 'purrr' was built under R version 4.3.1
## Warning: package 'dplyr' was built under R version 4.3.1
## Warning: package 'stringr' was built under R version 4.3.2
## Warning: package 'forcats' was built under R version 4.3.1
## Warning: package 'lubridate' was built under R version 4.3.1
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.2 ✔ readr 2.1.4
## ✔ forcats 1.0.0 ✔ stringr 1.5.0
## ✔ lubridate 1.9.2 ✔ tibble 3.2.1
## ✔ purrr 1.0.1 ✔ tidyr 1.3.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ tidyr::expand() masks reshape::expand()
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ✖ dplyr::rename() masks reshape::rename()
## ✖ lubridate::stamp() masks reshape::stamp()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(cfbfastR)
## Warning: package 'cfbfastR' was built under R version 4.3.1
# Load Functions:
source("sports_analytics_functions.r")
load("team_logos.rda")
The next few columns relate to the offensive performance of the school:
pass_n - Number of passes thrown
pass_yards - Total pass yards
pass_epa - Total pass EPA (Expected points added)
pass_wpa - Total pass WPA (Win probability added)
rush_n - Number of rushes
rush_yards - Total rush yards
rush_epa - Total rush EPA (Expected points added)
rush_wpa - Total Rush WPA (Win probability added)
We then have the offensive statistics at a per play level:
pass_yards_per_play - Pass yards per play
pass_epa_per_play - Pass EPA (Expected points added) per play
pass_wpa_per_play - Pass WPA (Win probability added) per play
rush_yards_per_play - Rush yards_per_play
rush_epa_per_play - Rush EPA (Expected points added) per play
rush_wpa_per_play - Rush WPA (Win probability added) per play
The next set of columns relate to the defensive performance of the school:
pass_n_def - Number of passes defended
pass_yards_def - Total pass yards against
pass_epa_def - Total pass EPA against (Expected points added)
pass_wpa_def - Total pass WPA against (Win probability added)
rush_n_def - Number of rushes defended
rush_yards_def - Total rush yards against
rush_epa_def - Total rush EPA against (Expected points added)
rush_wpa_def - Total rush WPA against (Win probability added)
Finally we have the defensive statistics at a per play level:
pass_yards_per_play_def - Pass yards against per play
pass_epa_per_play_def - Pass EPA against (Expected points added) per play
pass_wpa_per_play_def - Pass WPA against (Win probability added) per play
rush_yards_per_play_def - Rush yards against per play
rush_epa_per_play_def - Rush EPA against (Expected points added) per play
rush_wpa_per_play_def - Rush WPA against (Win probability added) per play
Setup
library(ggplot2) # Load ggplot2
library(ggdark) # Load ggdark
library(ggimage) # Load ggimage
library(reshape) # Load reshape
library(ggridges) # Load ggridges
library(ggrepel) # Load ggrepel
library(tidyverse)
library(cfbfastR)
Load 2015 Season
# Load data
cfb_2015 <- load_cfb_pbp(seasons = 2015)
# Convert to data frame
cfb_2015 <- as.data.frame(cfb_2015)
Play by Play
# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2015)
# Add summarized play type back to dataset
cfb_2015$playtype <- s_play_type
head(cfb_2015)
## year week id_play game_id game_play_number half_play_number
## 1 2015 1 4.006038e+17 400603827 1 1
## 2 2015 1 4.006038e+17 400603827 2 2
## 3 2015 1 4.006038e+17 400603827 3 3
## 4 2015 1 4.006038e+17 400603827 4 4
## 5 2015 1 4.006038e+17 400603827 5 5
## 6 2015 1 4.006038e+17 400603827 6 6
## drive_play_number pos_team def_pos_team pos_team_score def_pos_team_score
## 1 1 Alabama Wisconsin 0 0
## 2 2 Alabama Wisconsin 0 0
## 3 3 Alabama Wisconsin 0 0
## 4 4 Alabama Wisconsin 0 0
## 5 5 Alabama Wisconsin 0 0
## 6 6 Alabama Wisconsin 0 0
## half period clock.minutes clock.seconds play_type
## 1 1 1 15 0 Kickoff
## 2 1 1 14 45 Rush
## 3 1 1 14 15 Rush
## 4 1 1 13 55 Rush
## 5 1 1 13 15 Rush
## 6 1 1 12 40 Pass Reception
## play_text
## 1 Andrew Endicott kickoff for 61 yds , Kenyan Drake return for 16 yds to the Alab 20
## 2 Derrick Henry run for 4 yds to the Alab 24
## 3 Derrick Henry run for 5 yds to the Alab 29
## 4 Derrick Henry run for 3 yds to the Alab 32 for a 1ST down
## 5 Derrick Henry run for 1 yd to the Alab 33
## 6 Jake Coker pass complete to Robert Foster for 1 yd to the Alab 34
## down distance yards_to_goal yards_gained EPA ep_before ep_after
## 1 1 10 65 16 -0.4624372 0.8331681 0.3707309
## 2 1 10 80 4 -0.1516198 0.3707309 0.2191111
## 3 2 6 76 5 0.8101272 0.2191111 1.0292383
## 4 3 1 71 3 0.4974397 1.0292383 1.5266780
## 5 1 10 68 1 -0.7318541 1.5266780 0.7948239
## 6 2 9 67 1 -0.9457639 0.7948239 -0.1509401
## wpa wp_before wp_after def_wp_before def_wp_after penalty_detail
## 1 -0.0138547 0.4903198 0.4764651 0.5096802 0.5235349 <NA>
## 2 0.0028492 0.4764651 0.4793143 0.5235349 0.5206857 <NA>
## 3 0.0379041 0.4793143 0.5172184 0.5206857 0.4827816 <NA>
## 4 0.0297710 0.5172184 0.5469894 0.4827816 0.4530106 <NA>
## 5 -0.0220321 0.5469894 0.5249573 0.4530106 0.4750427 <NA>
## 6 -0.0333644 0.5249573 0.4915929 0.4750427 0.5084071 <NA>
## yds_penalty penalty_1st_conv new_series firstD_by_kickoff firstD_by_poss
## 1 NA FALSE 1 1 0
## 2 NA FALSE 0 0 1
## 3 NA FALSE 0 0 0
## 4 NA FALSE 0 0 0
## 5 NA FALSE 1 0 0
## 6 NA FALSE 0 0 0
## firstD_by_penalty firstD_by_yards def_EPA home_EPA away_EPA
## 1 0 0 0.4624372 -0.4624372 0.4624372
## 2 0 0 0.1516198 -0.1516198 0.1516198
## 3 0 0 -0.8101272 0.8101272 -0.8101272
## 4 0 0 -0.4974397 0.4974397 -0.4974397
## 5 0 1 0.7318541 -0.7318541 0.7318541
## 6 0 0 0.9457639 -0.9457639 0.9457639
## home_EPA_rush away_EPA_rush home_EPA_pass away_EPA_pass total_home_EPA
## 1 NA NA NA NA -0.46243724
## 2 -0.1516198 NA NA NA -0.61405706
## 3 0.8101272 NA NA NA 0.19607015
## 4 0.4974397 NA NA NA 0.69350985
## 5 -0.7318541 NA NA NA -0.03834422
## 6 NA NA -0.9457639 NA -0.98410817
## total_away_EPA total_home_EPA_rush total_away_EPA_rush total_home_EPA_pass
## 1 0.46243724 0.0000000 0 0.0000000
## 2 0.61405706 -0.1516198 0 0.0000000
## 3 -0.19607015 0.6585074 0 0.0000000
## 4 -0.69350985 1.1559471 0 0.0000000
## 5 0.03834422 0.4240930 0 0.0000000
## 6 0.98410817 0.4240930 0 -0.9457639
## total_away_EPA_pass net_home_EPA net_away_EPA net_home_EPA_rush
## 1 0 -0.92487447 0.92487447 0.0000000
## 2 0 -1.22811412 1.22811412 -0.1516198
## 3 0 0.39214030 -0.39214030 0.6585074
## 4 0 1.38701969 -1.38701969 1.1559471
## 5 0 -0.07668844 0.07668844 0.4240930
## 6 0 -1.96821633 1.96821633 0.4240930
## net_away_EPA_rush net_home_EPA_pass net_away_EPA_pass success epa_success
## 1 0.0000000 0.0000000 0.0000000 1 0
## 2 0.1516198 0.0000000 0.0000000 0 0
## 3 -0.6585074 0.0000000 0.0000000 1 1
## 4 -1.1559471 0.0000000 0.0000000 1 1
## 5 -0.4240930 0.0000000 0.0000000 0 0
## 6 -0.4240930 -0.9457639 0.9457639 0 0
## rz_play scoring_opp middle_8 stuffed_run change_of_pos_team downs_turnover
## 1 0 0 FALSE 0 0 0
## 2 0 0 FALSE 0 0 0
## 3 0 0 FALSE 0 0 0
## 4 0 0 FALSE 0 0 0
## 5 0 0 FALSE 0 0 0
## 6 0 0 FALSE 0 0 0
## turnover pos_score_diff_start pos_score_pts log_ydstogo ExpScoreDiff
## 1 0 0 0 2.302585 0.8331681
## 2 0 0 0 2.302585 0.3707309
## 3 0 0 0 1.791759 0.2191111
## 4 0 0 0 0.000000 1.0292383
## 5 0 0 0 2.302585 1.5266780
## 6 0 0 0 2.197225 0.7948239
## ExpScoreDiff_Time_Ratio half_clock.minutes TimeSecsRem adj_TimeSecsRem
## 1 2.313713e-04 30 1800 3600
## 2 1.033828e-04 29 1785 3585
## 3 6.161728e-05 29 1755 3555
## 4 2.910742e-04 28 1735 3535
## 5 4.366928e-04 28 1695 3495
## 6 2.296515e-04 27 1660 3460
## Goal_To_Go Under_two home away home_wp_before away_wp_before
## 1 FALSE FALSE Alabama Wisconsin 0.4903198 0.5096802
## 2 FALSE FALSE Alabama Wisconsin 0.4764651 0.5235349
## 3 FALSE FALSE Alabama Wisconsin 0.4793143 0.5206857
## 4 FALSE FALSE Alabama Wisconsin 0.5172184 0.4827816
## 5 FALSE FALSE Alabama Wisconsin 0.5469894 0.4530106
## 6 FALSE FALSE Alabama Wisconsin 0.5249573 0.4750427
## home_wp_after away_wp_after end_of_half pos_team_receives_2H_kickoff
## 1 0.4764651 0.5235349 0 0
## 2 0.4793143 0.5206857 0 0
## 3 0.5172184 0.4827816 0 0
## 4 0.5469894 0.4530106 0 0
## 5 0.5249573 0.4750427 0 0
## 6 0.4915929 0.5084071 0 0
## lead_pos_team lead_play_type lag_pos_team lag_play_type orig_play_type
## 1 Alabama Rush Alabama <NA> Kickoff
## 2 Alabama Rush Alabama Kickoff Rush
## 3 Alabama Rush Alabama Rush Rush
## 4 Alabama Rush Alabama Rush Rush
## 5 Alabama Pass Reception Alabama Rush Rush
## 6 Alabama Sack Alabama Rush Pass Reception
## Under_three down_end distance_end log_ydstogo_end yards_to_goal_end
## 1 FALSE 1 10 2.302585 80
## 2 FALSE 2 6 1.791759 76
## 3 FALSE 3 1 0.000000 71
## 4 FALSE 1 10 2.302585 68
## 5 FALSE 2 9 2.197225 67
## 6 FALSE 3 8 2.079442 66
## TimeSecsRem_end Goal_To_Go_end Under_two_end offense_score_play
## 1 1785 FALSE FALSE 0
## 2 1755 FALSE FALSE 0
## 3 1735 FALSE FALSE 0
## 4 1695 FALSE FALSE 0
## 5 1660 FALSE FALSE 0
## 6 1620 FALSE FALSE 0
## defense_score_play ppa yard_line scoring pos_team_timeouts_rem_before
## 1 0 NA 65 FALSE 3
## 2 0 -0.08843213 20 FALSE 3
## 3 0 0.08232293 24 FALSE 3
## 4 0 0.87285627 29 FALSE 3
## 5 0 -0.76582560 32 FALSE 3
## 6 0 -0.49575594 33 FALSE 3
## def_pos_team_timeouts_rem_before pos_team_timeouts def_pos_team_timeouts
## 1 3 3 3
## 2 3 3 3
## 3 3 3 3
## 4 3 3 3
## 5 3 3 3
## 6 3 3 3
## pos_score_diff pos_score_diff_start_end offense_play defense_play
## 1 0 0 Wisconsin Alabama
## 2 0 0 Alabama Wisconsin
## 3 0 0 Alabama Wisconsin
## 4 0 0 Alabama Wisconsin
## 5 0 0 Alabama Wisconsin
## 6 0 0 Alabama Wisconsin
## offense_receives_2H_kickoff change_of_poss score_pts score_diff_start
## 1 1 1 0 0
## 2 0 0 0 0
## 3 0 0 0 0
## 4 0 0 0 0
## 5 0 0 0 0
## 6 0 0 0 0
## score_diff offense_score defense_score offense_conference defense_conference
## 1 0 0 0 Big Ten SEC
## 2 0 0 0 SEC Big Ten
## 3 0 0 0 SEC Big Ten
## 4 0 0 0 SEC Big Ten
## 5 0 0 0 SEC Big Ten
## 6 0 0 0 SEC Big Ten
## off_timeout_called def_timeout_called offense_timeouts defense_timeouts
## 1 0 0 3 3
## 2 0 0 3 3
## 3 0 0 3 3
## 4 0 0 3 3
## 5 0 0 3 3
## 6 0 0 3 3
## off_timeouts_rem_before def_timeouts_rem_before rusher_player_name yds_rushed
## 1 3 3 <NA> NA
## 2 3 3 Derrick Henry 4
## 3 3 3 Derrick Henry 5
## 4 3 3 Derrick Henry 3
## 5 3 3 Derrick Henry 1
## 6 3 3 <NA> NA
## passer_player_name receiver_player_name yds_receiving yds_sacked sack_players
## 1 <NA> <NA> NA NA <NA>
## 2 <NA> <NA> NA NA <NA>
## 3 <NA> <NA> NA NA <NA>
## 4 <NA> <NA> NA NA <NA>
## 5 <NA> <NA> NA NA <NA>
## 6 Jake Coker Robert Foster 1 NA <NA>
## sack_player_name sack_player_name2 pass_breakup_player_name
## 1 <NA> <NA> <NA>
## 2 <NA> <NA> <NA>
## 3 <NA> <NA> <NA>
## 4 <NA> <NA> <NA>
## 5 <NA> <NA> <NA>
## 6 <NA> <NA> <NA>
## interception_player_name yds_int_return fumble_player_name
## 1 <NA> NA <NA>
## 2 <NA> NA <NA>
## 3 <NA> NA <NA>
## 4 <NA> NA <NA>
## 5 <NA> NA <NA>
## 6 <NA> NA <NA>
## fumble_forced_player_name fumble_recovered_player_name yds_fumble_return
## 1 <NA> <NA> NA
## 2 <NA> <NA> NA
## 3 <NA> <NA> NA
## 4 <NA> <NA> NA
## 5 <NA> <NA> NA
## 6 <NA> <NA> NA
## punter_player_name yds_punted punt_returner_player_name yds_punt_return
## 1 <NA> NA <NA> NA
## 2 <NA> NA <NA> NA
## 3 <NA> NA <NA> NA
## 4 <NA> NA <NA> NA
## 5 <NA> NA <NA> NA
## 6 <NA> NA <NA> NA
## yds_punt_gained punt_block_player_name punt_block_return_player_name
## 1 NA <NA> <NA>
## 2 NA <NA> <NA>
## 3 NA <NA> <NA>
## 4 NA <NA> <NA>
## 5 NA <NA> <NA>
## 6 NA <NA> <NA>
## fg_kicker_player_name yds_fg fg_block_player_name fg_return_player_name
## 1 <NA> NA <NA> <NA>
## 2 <NA> NA <NA> <NA>
## 3 <NA> NA <NA> <NA>
## 4 <NA> NA <NA> <NA>
## 5 <NA> NA <NA> <NA>
## 6 <NA> NA <NA> <NA>
## kickoff_player_name yds_kickoff kickoff_returner_player_name
## 1 Andrew Endicott 61 Kenyan Drake
## 2 <NA> NA <NA>
## 3 <NA> NA <NA>
## 4 <NA> NA <NA>
## 5 <NA> NA <NA>
## 6 <NA> NA <NA>
## yds_kickoff_return new_id orig_drive_number drive_number
## 1 16 101849902 1 1
## 2 NA 101855401 1 1
## 3 NA 101858401 1 1
## 4 NA 101864401 1 1
## 5 NA 101868401 1 1
## 6 NA 101875901 1 1
## drive_result_detailed new_drive_pts drive_id drive_result
## 1 Punt 0 4006038271 PUNT
## 2 Punt 0 4006038271 PUNT
## 3 Punt 0 4006038271 PUNT
## 4 Punt 0 4006038271 PUNT
## 5 Punt 0 4006038271 PUNT
## 6 Punt 0 4006038271 PUNT
## drive_start_yards_to_goal drive_end_yards_to_goal drive_yards drive_scoring
## 1 80 76 4 0
## 2 80 76 4 0
## 3 80 76 4 0
## 4 80 76 4 0
## 5 80 76 4 0
## 6 80 76 4 0
## drive_pts drive_start_period drive_end_period drive_time_minutes_start
## 1 0 1 1 15
## 2 0 1 1 15
## 3 0 1 1 15
## 4 0 1 1 15
## 5 0 1 1 15
## 6 0 1 1 15
## drive_time_seconds_start drive_time_minutes_end drive_time_seconds_end
## 1 0 11 11
## 2 0 11 11
## 3 0 11 11
## 4 0 11 11
## 5 0 11 11
## 6 0 11 11
## drive_time_minutes_elapsed drive_time_seconds_elapsed drive_numbers
## 1 3 49 1
## 2 3 49 0
## 3 3 49 0
## 4 3 49 0
## 5 3 49 0
## 6 3 49 0
## number_of_drives pts_scored drive_result_detailed_flag drive_result2
## 1 1 0 <NA> PUNT
## 2 1 0 <NA> PUNT
## 3 1 0 <NA> PUNT
## 4 1 0 <NA> PUNT
## 5 1 0 <NA> PUNT
## 6 1 0 <NA> PUNT
## drive_num lag_drive_result_detailed lead_drive_result_detailed
## 1 1 <NA> Punt
## 2 1 Punt Punt
## 3 1 Punt Punt
## 4 1 Punt Punt
## 5 1 Punt Punt
## 6 1 Punt Punt
## lag_new_drive_pts id_drive rush rush_td pass pass_td completion
## 1 NA 4006038271 0 0 0 0 0
## 2 0 4006038271 1 0 0 0 0
## 3 0 4006038271 1 0 0 0 0
## 4 0 4006038271 1 0 0 0 0
## 5 0 4006038271 1 0 0 0 0
## 6 0 4006038271 0 0 1 0 1
## pass_attempt target sack_vec sack int int_td turnover_vec turnover_vec_lag
## 1 0 0 0 0 0 0 0 NA
## 2 0 0 0 0 0 0 0 0
## 3 0 0 0 0 0 0 0 0
## 4 0 0 0 0 0 0 0 0
## 5 0 0 0 0 0 0 0 0
## 6 1 1 0 0 0 0 0 0
## turnover_indicator kickoff_play receives_2H_kickoff missing_yard_flag
## 1 0 1 0 FALSE
## 2 0 0 0 FALSE
## 3 0 0 0 FALSE
## 4 0 0 0 FALSE
## 5 0 0 0 FALSE
## 6 0 0 0 FALSE
## scoring_play td_play touchdown safety fumble_vec kickoff_tb kickoff_onside
## 1 0 0 0 0 0 0 0
## 2 0 0 0 0 0 0 0
## 3 0 0 0 0 0 0 0
## 4 0 0 0 0 0 0 0
## 5 0 0 0 0 0 0 0
## 6 0 0 0 0 0 0 0
## kickoff_oob kickoff_fair_catch kickoff_downed kickoff_safety kick_play punt
## 1 0 0 0 0 1 0
## 2 0 0 0 0 0 0
## 3 0 0 0 0 0 0
## 4 0 0 0 0 0 0
## 5 0 0 0 0 0 0
## 6 0 0 0 0 0 0
## punt_play punt_tb punt_oob punt_fair_catch punt_downed punt_safety
## 1 0 0 0 0 0 0
## 2 0 0 0 0 0 0
## 3 0 0 0 0 0 0
## 4 0 0 0 0 0 0
## 5 0 0 0 0 0 0
## 6 0 0 0 0 0 0
## punt_blocked penalty_safety fg_inds fg_made fg_make_prob No_Score_before
## 1 0 0 0 FALSE NA 0.001775325
## 2 0 0 0 FALSE NA 0.002281596
## 3 0 0 0 FALSE NA 0.002551793
## 4 0 0 0 FALSE NA 0.002566175
## 5 0 0 0 FALSE NA 0.002786976
## 6 0 0 0 FALSE NA 0.003397270
## FG_before Opp_FG_before Opp_Safety_before Opp_TD_before Safety_before
## 1 0.1703466 0.08920160 0.001915283 0.2604038 0.002949699
## 2 0.1348319 0.12125747 0.004405305 0.3433359 0.003009137
## 3 0.1282595 0.12410270 0.003756808 0.3542328 0.003178192
## 4 0.1004425 0.11424110 0.002285337 0.3123559 0.003013381
## 5 0.1626506 0.09365869 0.002379197 0.2735665 0.003056112
## 6 0.1568627 0.10705790 0.002747635 0.3173046 0.003274382
## TD_before No_Score_after FG_after Opp_FG_after Opp_Safety_after Opp_TD_after
## 1 0.4734077 0.002281596 0.1348319 0.12125747 0.004405305 0.3433359
## 2 0.3908787 0.002551793 0.1282595 0.12410270 0.003756808 0.3542328
## 3 0.3839182 0.002566175 0.1004425 0.11424110 0.002285337 0.3123559
## 4 0.4650956 0.002786976 0.1626506 0.09365869 0.002379197 0.2735665
## 5 0.4619019 0.003397270 0.1568627 0.10705790 0.002747635 0.3173046
## 6 0.4093555 0.004085967 0.1371961 0.12685121 0.003444598 0.3753826
## Safety_after TD_after position_reception position_target position_completion
## 1 0.003009137 0.3908787 <NA> <NA> <NA>
## 2 0.003178192 0.3839182 <NA> <NA> <NA>
## 3 0.003013381 0.4650956 <NA> <NA> <NA>
## 4 0.003056112 0.4619019 <NA> <NA> <NA>
## 5 0.003274382 0.4093555 <NA> <NA> <NA>
## 6 0.003736811 0.3493027 WR WR QB
## position_incompletion position_sack_taken position_sack
## 1 <NA> <NA> <NA>
## 2 <NA> <NA> <NA>
## 3 <NA> <NA> <NA>
## 4 <NA> <NA> <NA>
## 5 <NA> <NA> <NA>
## 6 <NA> <NA> <NA>
## position_interception_thrown position_interception position_fumble
## 1 <NA> <NA> <NA>
## 2 <NA> <NA> <NA>
## 3 <NA> <NA> <NA>
## 4 <NA> <NA> <NA>
## 5 <NA> <NA> <NA>
## 6 <NA> <NA> <NA>
## position_fumble_forced position_fumble_recovered position_pass_breakup
## 1 <NA> <NA> <NA>
## 2 <NA> <NA> <NA>
## 3 <NA> <NA> <NA>
## 4 <NA> <NA> <NA>
## 5 <NA> <NA> <NA>
## 6 <NA> <NA> <NA>
## position_rush position_touchdown season opponent team_score opponent_score
## 1 <NA> <NA> NA <NA> NA NA
## 2 RB <NA> 2015 Wisconsin 0 0
## 3 RB <NA> 2015 Wisconsin 0 0
## 4 RB <NA> 2015 Wisconsin 0 0
## 5 RB <NA> 2015 Wisconsin 0 0
## 6 <NA> <NA> 2015 Wisconsin 0 0
## rush_player_id rush_player rush_yds reception_player_id reception_player
## 1 NA <NA> NA NA <NA>
## 2 546368 Derrick Henry 4 NA <NA>
## 3 546368 Derrick Henry 5 NA <NA>
## 4 546368 Derrick Henry 3 NA <NA>
## 5 546368 Derrick Henry 1 NA <NA>
## 6 NA <NA> NA 551293 Robert Foster
## reception_yds completion_player_id completion_player completion_yds
## 1 NA NA <NA> NA
## 2 NA NA <NA> NA
## 3 NA NA <NA> NA
## 4 NA NA <NA> NA
## 5 NA NA <NA> NA
## 6 1 514124 Jake Coker 1
## interception_player_id interception_player interception_stat
## 1 NA <NA> NA
## 2 NA <NA> NA
## 3 NA <NA> NA
## 4 NA <NA> NA
## 5 NA <NA> NA
## 6 NA <NA> NA
## interception_thrown_player_id interception_thrown_player
## 1 NA <NA>
## 2 NA <NA>
## 3 NA <NA>
## 4 NA <NA>
## 5 NA <NA>
## 6 NA <NA>
## interception_thrown_stat touchdown_player_id touchdown_player touchdown_stat
## 1 NA NA <NA> NA
## 2 NA NA <NA> NA
## 3 NA NA <NA> NA
## 4 NA NA <NA> NA
## 5 NA NA <NA> NA
## 6 NA NA <NA> NA
## incompletion_player_id incompletion_player incompletion_stat target_player_id
## 1 NA <NA> NA NA
## 2 NA <NA> NA NA
## 3 NA <NA> NA NA
## 4 NA <NA> NA NA
## 5 NA <NA> NA NA
## 6 NA <NA> NA NA
## target_player target_stat fumble_recovered_player_id fumble_recovered_player
## 1 <NA> NA NA <NA>
## 2 <NA> NA NA <NA>
## 3 <NA> NA NA <NA>
## 4 <NA> NA NA <NA>
## 5 <NA> NA NA <NA>
## 6 <NA> NA NA <NA>
## fumble_recovered_stat fumble_forced_player_id fumble_forced_player
## 1 NA NA <NA>
## 2 NA NA <NA>
## 3 NA NA <NA>
## 4 NA NA <NA>
## 5 NA NA <NA>
## 6 NA NA <NA>
## fumble_forced_stat fumble_player_id fumble_player fumble_stat sack_player_id
## 1 NA NA <NA> NA NA
## 2 NA NA <NA> NA NA
## 3 NA NA <NA> NA NA
## 4 NA NA <NA> NA NA
## 5 NA NA <NA> NA NA
## 6 NA NA <NA> NA NA
## sack_player sack_stat sack_taken_player_id sack_taken_player sack_taken_stat
## 1 <NA> NA NA <NA> NA
## 2 <NA> NA NA <NA> NA
## 3 <NA> NA NA <NA> NA
## 4 <NA> NA NA <NA> NA
## 5 <NA> NA NA <NA> NA
## 6 <NA> NA NA <NA> NA
## pass_breakup_player_id pass_breakup_player pass_breakup_stat play_id
## 1 NA <NA> NA NA
## 2 NA <NA> NA NA
## 3 NA <NA> NA NA
## 4 NA <NA> NA NA
## 5 NA <NA> NA NA
## 6 NA <NA> NA NA
## penalty_flag penalty_declined penalty_no_play penalty_offset penalty_text
## 1 FALSE FALSE FALSE FALSE FALSE
## 2 FALSE FALSE FALSE FALSE FALSE
## 3 FALSE FALSE FALSE FALSE FALSE
## 4 FALSE FALSE FALSE FALSE FALSE
## 5 FALSE FALSE FALSE FALSE FALSE
## 6 FALSE FALSE FALSE FALSE FALSE
## penalty_play_text row drive_event_number lag_play_type2 lag_play_type3
## 1 <NA> 1 1 <NA> <NA>
## 2 <NA> 2 2 <NA> <NA>
## 3 <NA> 3 3 Kickoff <NA>
## 4 <NA> 4 4 Rush Kickoff
## 5 <NA> 5 5 Rush Rush
## 6 <NA> 6 6 Rush Rush
## lag_play_text
## 1 <NA>
## 2 Andrew Endicott kickoff for 61 yds , Kenyan Drake return for 16 yds to the Alab 20
## 3 Derrick Henry run for 4 yds to the Alab 24
## 4 Derrick Henry run for 5 yds to the Alab 29
## 5 Derrick Henry run for 3 yds to the Alab 32 for a 1ST down
## 6 Derrick Henry run for 1 yd to the Alab 33
## lag_play_text2
## 1 <NA>
## 2 <NA>
## 3 Andrew Endicott kickoff for 61 yds , Kenyan Drake return for 16 yds to the Alab 20
## 4 Derrick Henry run for 4 yds to the Alab 24
## 5 Derrick Henry run for 5 yds to the Alab 29
## 6 Derrick Henry run for 3 yds to the Alab 32 for a 1ST down
## lead_play_text
## 1 Derrick Henry run for 4 yds to the Alab 24
## 2 Derrick Henry run for 5 yds to the Alab 29
## 3 Derrick Henry run for 3 yds to the Alab 32 for a 1ST down
## 4 Derrick Henry run for 1 yd to the Alab 33
## 5 Jake Coker pass complete to Robert Foster for 1 yd to the Alab 34
## 6 Jake Coker sacked by Joe Schobert for a loss of 10 yards to the Alab 24
## lag_first_by_penalty lag_first_by_penalty2 lag_first_by_yards
## 1 0 NA 0
## 2 0 NA 0
## 3 0 0 0
## 4 0 0 0
## 5 0 0 1
## 6 0 0 0
## lag_first_by_yards2 first_by_penalty first_by_yards play_after_turnover
## 1 NA 0 0 NA
## 2 NA 0 0 0
## 3 0 0 0 0
## 4 0 0 1 0
## 5 0 0 0 0
## 6 1 0 0 0
## lag_change_of_poss lag_change_of_pos_team lag_change_of_pos_team2
## 1 0 0 0
## 2 1 0 0
## 3 0 0 0
## 4 0 0 0
## 5 0 0 0
## 6 0 0 0
## lag_kickoff_play lag_punt lag_punt2 lag_scoring_play lag_turnover_vec
## 1 0 0 0 0 0
## 2 1 0 0 0 0
## 3 0 0 0 0 0
## 4 0 0 0 0 0
## 5 0 0 0 0 0
## 6 0 0 0 0 0
## lag_downs_turnover lag_defense_score_play playtype
## 1 0 NA Kickoff
## 2 0 0 Rush
## 3 0 0 Rush
## 4 0 0 Rush
## 5 0 0 Rush
## 6 0 0 Pass
2015 Notre Dame Offensive Stats
nd_off_2015 <- cfb_2015[which(cfb_2015$pos_team == "Notre Dame" &
cfb_2015$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_off_2015_m <- melt(nd_off_2015[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2015 ND Offense EPA
# Create plot
ggplot(nd_off_2015_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "Notre Dame Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.727
# Turn off dark mode
invert_geom_defaults()
2015 Notre Dame Defense
nd_def_2015 <- cfb_2015[which(cfb_2015$def_pos_team == "Notre Dame" &
cfb_2015$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_def_2015_m <- melt(nd_def_2015[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2015 ND Defense EPA
# Create plot
ggplot(nd_def_2015_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "Notre Dame Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.637
# Turn off dark mode
invert_geom_defaults()
By Field Position
y1_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$yards_to_goal >= 80,],
team_logos = team_logos)
# Calculate statistics for 20 - 50
y2_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$yards_to_goal >= 50 &
cfb_2015$yards_to_goal < 80,],
team_logos = team_logos)
# Calculate statistics for 50 - 79
y3_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$yards_to_goal >= 21 &
cfb_2015$yards_to_goal < 50,],
team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$yards_to_goal >= 0 &
cfb_2015$yards_to_goal < 20,],
team_logos = team_logos)
2015 Notre Dame
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_yards_2015 <- rbind.data.frame(y1_2015_stats[y1_2015_stats$school == team,],
y2_2015_stats[y2_2015_stats$school == team,],
y3_2015_stats[y3_2015_stats$school == team,],
y4_2015_stats[y4_2015_stats$school == team,])
# Create plot for offensive EPA
nd_epa_off_2015 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = team, # Set Team
res_1 = nd_by_yards_2015, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
## Warning: The `size` argument of `element_rect()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# Generate plot for offensive EPA
nd_epa_off_2015
# Create plot for offensive yards
nd_yards_2015 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2015, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2015
2015 Defensive Stats
# Create plot for defensive EPA
nd_epa_def_2015 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2015, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2015
# Yards
# Create plot for defensive yards
nd_yards_2015_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2015, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2015_def
2015 Michigan Offensive Stats
mich_off_2015 <- cfb_2015[which(cfb_2015$pos_team == "Michigan" &
cfb_2015$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_off_2015_m <- melt(mich_off_2015[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2015 Michigan Offense EPA
# Create plot
ggplot(mich_off_2015_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "Michigan Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.789
## Warning: Removed 2 rows containing non-finite values (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
2015 Michigan Defense
mich_def_2015 <- cfb_2015[which(cfb_2015$def_pos_team == "Michigan" &
cfb_2015$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_def_2015_m <- melt(mich_def_2015[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2015 Michigan Defense EPA
# Create plot
ggplot(mich_def_2015_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2015 Michigan Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.662
## Warning: Removed 10 rows containing non-finite values
## (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
By Field Position
2015 Michigan Offense
# Set team as Notre Dame
team <- "Michigan"
# Extract Notre Dame statistics
mich_by_yards_2015 <- rbind.data.frame(y1_2015_stats[y1_2015_stats$school == team,],
y2_2015_stats[y2_2015_stats$school == team,],
y3_2015_stats[y3_2015_stats$school == team,],
y4_2015_stats[y4_2015_stats$school == team,])
# Create plot for offensive EPA
mich_epa_off_2015 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2015, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2015
# Create plot for offensive yards
mich_yards_2015 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2015, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2015
2015 Michigan Defensive Stats
# Create plot for defensive EPA
mich_epa_def_2015 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2015, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2015
# Yards
# Create plot for defensive yards
mich_yards_2015_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2015, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2015_def
# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2015$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2015$zone_pass_num <- paste(nd_by_yards_2015$zone, ": ", nd_by_yards_2015$pass_n, sep = "")
nd_by_yards_2015$zone_rush_num <- paste(nd_by_yards_2015$zone, ": ", nd_by_yards_2015$rush_n, sep = "")
nd_by_yards_2015$zone_pass_num_def <- paste(nd_by_yards_2015$zone, ": ", nd_by_yards_2015$pass_n_def, sep = "")
nd_by_yards_2015$zone_rush_num_def <- paste(nd_by_yards_2015$zone, ": ", nd_by_yards_2015$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_yards_2015$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2015$zone_pass_num <- paste(mich_by_yards_2015$zone, ": ", mich_by_yards_2015$pass_n, sep = "")
mich_by_yards_2015$zone_rush_num <- paste(mich_by_yards_2015$zone, ": ", mich_by_yards_2015$rush_n, sep = "")
mich_by_yards_2015$zone_pass_num_def <- paste(mich_by_yards_2015$zone, ": ", mich_by_yards_2015$pass_n_def, sep = "")
mich_by_yards_2015$zone_rush_num_def <- paste(mich_by_yards_2015$zone, ": ", mich_by_yards_2015$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2015 <- cbind.data.frame(nd_by_yards_2015$zone,
((nd_by_yards_2015$pass_wpa_per_play + mich_by_yards_2015$pass_wpa_per_play_def)/2),
((nd_by_yards_2015$pass_epa_per_play + mich_by_yards_2015$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2015) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2015 <- cbind.data.frame(nd_by_yards_2015$zone,
((nd_by_yards_2015$rush_wpa_per_play + mich_by_yards_2015$rush_wpa_per_play_def)/2),
((nd_by_yards_2015$rush_epa_per_play + mich_by_yards_2015$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2015) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2015 <- cbind.data.frame(mich_by_yards_2015$zone,
((mich_by_yards_2015$pass_wpa_per_play + nd_by_yards_2015$pass_wpa_per_play_def)/2),
((mich_by_yards_2015$pass_epa_per_play + nd_by_yards_2015$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2015) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2015 <- cbind.data.frame(mich_by_yards_2015$zone,
((mich_by_yards_2015$rush_wpa_per_play + nd_by_yards_2015$rush_wpa_per_play_def)/2),
((mich_by_yards_2015$rush_epa_per_play + nd_by_yards_2015$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2015) <- c("zone", "exp_wpa", "exp_epa")
WPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2015_1 <- ggplot(data = nd_v_mich_pass_2015, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2015_1
# ND Rushing Offense vs Michigan Rushing Defense
g_2015_2 <- ggplot(data = nd_v_mich_rush_2015, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2015_2
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2015_3 <- ggplot(data = mich_v_nd_pass_2015, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2015_3
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2015_4 <- ggplot(data = mich_v_nd_rush_2015, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2015_4
According to WPA, Notre Dame in 2015 would be about equally as
successful either running or passing the ball over Michigan, while
Michigan in 2015 would matchup well against the Irish in the passing
attack. Best individual matchup would be Michigan’s passing offense
against Notre Dame’s passing defense.
EPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2015_5 <- ggplot(data = nd_v_mich_pass_2015, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2015_5
# ND Rushing Offense vs Michigan Rushing Defense
g_2015_6 <- ggplot(data = nd_v_mich_rush_2015, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2015_6
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2015_7 <- ggplot(data = mich_v_nd_pass_2015, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2015_7
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2015_8 <- ggplot(data = mich_v_nd_rush_2015, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2015_8
According to EPA, Notre Dame would be much wiser to run the ball on
Michigan while Michigan should focus on passing the ball more against
Notre Dame in 2015. Michigan’s passing attack against ND’s pass defense
is the most favorable individual matchup.
Extracting Plays by Downs
z1_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$down == 1,],
team_logos = team_logos)
# Calculate statistics for 2nd Down
z2_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$down == 2,],
team_logos = team_logos)
# Calculate statistics for 3rd Down
z3_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$down == 3,],
team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2015_stats <- create_team_summary(pbp = cfb_2015[cfb_2015$down == 4,],
team_logos = team_logos)
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_down_2015 <- rbind.data.frame(z1_2015_stats[z1_2015_stats$school == team,],
z2_2015_stats[z2_2015_stats$school == team,],
z3_2015_stats[z3_2015_stats$school == team,],
z4_2015_stats[z4_2015_stats$school == team,])
# Set team as Michigan
team <- "Michigan"
# Extract Michigan statistics
mich_by_down_2015 <- rbind.data.frame(z1_2015_stats[z1_2015_stats$school == team,],
z2_2015_stats[z2_2015_stats$school == team,],
z3_2015_stats[z3_2015_stats$school == team,],
z4_2015_stats[z4_2015_stats$school == team,])
Setup Comparisons by Down
# ND Pass and Rush Offense and Defense
nd_by_down_2015$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2015$down_pass_num <- paste(nd_by_down_2015$down, ": ", nd_by_down_2015$pass_n, sep = "")
nd_by_down_2015$down_rush_num <- paste(nd_by_down_2015$down, ": ", nd_by_down_2015$rush_n, sep = "")
nd_by_down_2015$down_pass_num_def <- paste(nd_by_down_2015$down, ": ", nd_by_down_2015$pass_n_def, sep = "")
nd_by_down_2015$down_rush_num_def <- paste(nd_by_down_2015$down, ": ", nd_by_down_2015$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_down_2015$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2015$down_pass_num <- paste(mich_by_down_2015$down, ": ", mich_by_down_2015$pass_n, sep = "")
mich_by_down_2015$down_rush_num <- paste(mich_by_down_2015$down, ": ", mich_by_down_2015$rush_n, sep = "")
mich_by_down_2015$down_pass_num_def <- paste(mich_by_down_2015$down, ": ", mich_by_down_2015$pass_n_def, sep = "")
mich_by_down_2015$down_rush_num_def <- paste(mich_by_down_2015$down, ": ", mich_by_down_2015$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2015_down <- cbind.data.frame(nd_by_down_2015$down,
((nd_by_down_2015$pass_wpa_per_play + mich_by_down_2015$pass_wpa_per_play_def)/2),
((nd_by_down_2015$pass_epa_per_play + mich_by_down_2015$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2015_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2015_down <- cbind.data.frame(nd_by_down_2015$down,
((nd_by_down_2015$rush_wpa_per_play + mich_by_down_2015$rush_wpa_per_play_def)/2),
((nd_by_down_2015$rush_epa_per_play + mich_by_down_2015$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2015_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2015_down <- cbind.data.frame(mich_by_down_2015$down,
((mich_by_down_2015$pass_wpa_per_play + nd_by_down_2015$pass_wpa_per_play_def)/2),
((mich_by_down_2015$pass_epa_per_play + nd_by_down_2015$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2015_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2015_down <- cbind.data.frame(mich_by_down_2015$down,
((mich_by_down_2015$rush_wpa_per_play + nd_by_down_2015$rush_wpa_per_play_def)/2),
((mich_by_down_2015$rush_epa_per_play + nd_by_down_2015$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2015_down) <- c("down", "exp_wpa", "exp_epa")
WPA Graphs by Down
# ND Passing Offense vs Michigan Passing Defense
d_2015_1 <- ggplot(data = nd_v_mich_pass_2015_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2015_1
# ND Rushing Offense vs Michigan Rushing Defense
d_2015_2 <- ggplot(data = nd_v_mich_rush_2015_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2015_2
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2015_3 <- ggplot(data = mich_v_nd_pass_2015_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2015_3
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2015_4 <- ggplot(data = mich_v_nd_rush_2015_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2015_4
Looking at it by down WPA reinforces that Notre Dame’s offense would be
best served as a balanced attack, while Michigan would be much better
suited to pass the ball against ND’s defense.
EPA by Down
# ND Passing Offense vs Michigan Passing Defense
d_2015_5 <- ggplot(data = nd_v_mich_pass_2015_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2015_5
# ND Rushing Offense vs Michigan Rushing Defense
d_2015_6 <- ggplot(data = nd_v_mich_rush_2015_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2015_6
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2015_7 <- ggplot(data = mich_v_nd_pass_2015_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2015_7
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2015_8 <- ggplot(data = mich_v_nd_rush_2015_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2015 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2015_8
EPA by down paints a scenario where Notre Dame is more likely to have success running the ball on Michigan as opposed to relying on their passing game, while Michigan’s passing attack once again garners a more favored outlook compared to their rushing attack.
Load 2016 Season
# Load data
cfb_2016 <- load_cfb_pbp(seasons = 2016)
# Convert to data frame
cfb_2016 <- as.data.frame(cfb_2016)
Play by Play
# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2016)
# Add summarized play type back to dataset
cfb_2016$playtype <- s_play_type
2016 Notre Dame Offensive Stats
nd_off_2016 <- cfb_2016[which(cfb_2016$pos_team == "Notre Dame" &
cfb_2016$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_off_2016_m <- melt(nd_off_2016[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2016 ND Offense EPA
# Create plot
ggplot(nd_off_2016_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2016 Notre Dame Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.584
## Warning: Removed 4 rows containing non-finite values (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
2016 Notre Dame Defense
nd_def_2016 <- cfb_2016[which(cfb_2016$def_pos_team == "Notre Dame" &
cfb_2016$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_def_2016_m <- melt(nd_def_2016[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2016 ND Defense EPA
# Create plot
ggplot(nd_def_2016_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2016 Notre Dame Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.675
## Warning: Removed 9 rows containing non-finite values (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
By Field Position
y1_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$yards_to_goal >= 80,],
team_logos = team_logos)
# Calculate statistics for 20 - 50
y2_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$yards_to_goal >= 50 &
cfb_2016$yards_to_goal < 80,],
team_logos = team_logos)
# Calculate statistics for 50 - 79
y3_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$yards_to_goal >= 21 &
cfb_2016$yards_to_goal < 50,],
team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$yards_to_goal >= 0 &
cfb_2016$yards_to_goal < 20,],
team_logos = team_logos)
2016 Notre Dame Offense by Field Position
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_yards_2016 <- rbind.data.frame(y1_2016_stats[y1_2016_stats$school == team,],
y2_2016_stats[y2_2016_stats$school == team,],
y3_2016_stats[y3_2016_stats$school == team,],
y4_2016_stats[y4_2016_stats$school == team,])
# Create plot for offensive EPA
nd_epa_off_2016 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2016, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2016
# Create plot for offensive yards
nd_yards_2016 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2016, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2016
2016 Notre Dame defense by field position
# Create plot for defensive EPA
nd_epa_def_2016 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2016, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2016
# Yards
# Create plot for defensive yards
nd_yards_2016_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2016, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2016_def
2016 Michigan Offensive Stats
mich_off_2016 <- cfb_2016[which(cfb_2016$pos_team == "Michigan" &
cfb_2016$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_off_2016_m <- melt(mich_off_2016[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2016 Michigan Offense EPA
# Create plot
ggplot(mich_off_2016_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2016 Michigan Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.665
## Warning: Removed 10 rows containing non-finite values
## (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
2016 Michigan Defense
mich_def_2016 <- cfb_2016[which(cfb_2016$def_pos_team == "Michigan" &
cfb_2016$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_def_2016_m <- melt(mich_def_2016[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2016 Michigan Defense EPA
# Create plot
ggplot(mich_def_2016_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2016 Michigan Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.584
## Warning: Removed 7 rows containing non-finite values (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
By Field Position
2016 Michigan Offense
# Set team as Notre Dame
team <- "Michigan"
# Extract Notre Dame statistics
mich_by_yards_2016 <- rbind.data.frame(y1_2016_stats[y1_2016_stats$school == team,],
y2_2016_stats[y2_2016_stats$school == team,],
y3_2016_stats[y3_2016_stats$school == team,],
y4_2016_stats[y4_2016_stats$school == team,])
# Create plot for offensive EPA
mich_epa_off_2016 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2016, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2016
# Create plot for offensive yards
mich_yards_2016 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2016, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2016
# Create plot for defensive EPA
mich_epa_def_2016 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2016, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2016
# Yards
# Create plot for defensive yards
mich_yards_2016_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2016, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2016_def
# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2016$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2016$zone_pass_num <- paste(nd_by_yards_2016$zone, ": ", nd_by_yards_2016$pass_n, sep = "")
nd_by_yards_2016$zone_rush_num <- paste(nd_by_yards_2016$zone, ": ", nd_by_yards_2016$rush_n, sep = "")
nd_by_yards_2016$zone_pass_num_def <- paste(nd_by_yards_2016$zone, ": ", nd_by_yards_2016$pass_n_def, sep = "")
nd_by_yards_2016$zone_rush_num_def <- paste(nd_by_yards_2016$zone, ": ", nd_by_yards_2016$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_yards_2016$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2016$zone_pass_num <- paste(mich_by_yards_2016$zone, ": ", mich_by_yards_2016$pass_n, sep = "")
mich_by_yards_2016$zone_rush_num <- paste(mich_by_yards_2016$zone, ": ", mich_by_yards_2016$rush_n, sep = "")
mich_by_yards_2016$zone_pass_num_def <- paste(mich_by_yards_2016$zone, ": ", mich_by_yards_2016$pass_n_def, sep = "")
mich_by_yards_2016$zone_rush_num_def <- paste(mich_by_yards_2016$zone, ": ", mich_by_yards_2016$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2016 <- cbind.data.frame(nd_by_yards_2016$zone,
((nd_by_yards_2016$pass_wpa_per_play + mich_by_yards_2016$pass_wpa_per_play_def)/2),
((nd_by_yards_2016$pass_epa_per_play + mich_by_yards_2016$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2016) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2016 <- cbind.data.frame(nd_by_yards_2016$zone,
((nd_by_yards_2016$rush_wpa_per_play + mich_by_yards_2016$rush_wpa_per_play_def)/2),
((nd_by_yards_2016$rush_epa_per_play + mich_by_yards_2016$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2016) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2016 <- cbind.data.frame(mich_by_yards_2016$zone,
((mich_by_yards_2016$pass_wpa_per_play + nd_by_yards_2016$pass_wpa_per_play_def)/2),
((mich_by_yards_2016$pass_epa_per_play + nd_by_yards_2016$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2016) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2016 <- cbind.data.frame(mich_by_yards_2016$zone,
((mich_by_yards_2016$rush_wpa_per_play + nd_by_yards_2016$rush_wpa_per_play_def)/2),
((mich_by_yards_2016$rush_epa_per_play + nd_by_yards_2016$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2016) <- c("zone", "exp_wpa", "exp_epa")
WPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2016_1 <- ggplot(data = nd_v_mich_pass_2016, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2016_1
# ND Rushing Offense vs Michigan Rushing Defense
g_2016_2 <- ggplot(data = nd_v_mich_rush_2016, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2016_2
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2016_3 <- ggplot(data = mich_v_nd_pass_2016, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2016_3
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2016_4 <- ggplot(data = mich_v_nd_rush_2016, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2016_4
According to WPA, this paints a grim matchup for Notre Dame against
Michigan. Notre Dame’s offense would need to be balanced, while Michigan
could practically run whatever they want on offense because they sweep
all matchups against Notre Dame’s defense.
EPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2016_5 <- ggplot(data = nd_v_mich_pass_2016, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2016_5
# ND Rushing Offense vs Michigan Rushing Defense
g_2016_6 <- ggplot(data = nd_v_mich_rush_2016, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2016_6
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2016_7 <- ggplot(data = mich_v_nd_pass_2016, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2016_7
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2016_8 <- ggplot(data = mich_v_nd_rush_2016, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2016_8
EPA paints an even more grim matchup for the Irish, as the Irish offense
wins less than half of the total matchups against Michigan’s defense,
while the Wolverines win every conceivable matchup against ND. Both
should prioritize throwing the ball as opposed to running it.
Extracting Plays by Downs
z1_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$down == 1,],
team_logos = team_logos)
# Calculate statistics for 2nd Down
z2_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$down == 2,],
team_logos = team_logos)
# Calculate statistics for 3rd Down
z3_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$down == 3,],
team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2016_stats <- create_team_summary(pbp = cfb_2016[cfb_2016$down == 4,],
team_logos = team_logos)
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_down_2016 <- rbind.data.frame(z1_2016_stats[z1_2016_stats$school == team,],
z2_2016_stats[z2_2016_stats$school == team,],
z3_2016_stats[z3_2016_stats$school == team,],
z4_2016_stats[z4_2016_stats$school == team,])
# Set team as Michigan
team <- "Michigan"
# Extract Michigan statistics
mich_by_down_2016 <- rbind.data.frame(z1_2016_stats[z1_2016_stats$school == team,],
z2_2016_stats[z2_2016_stats$school == team,],
z3_2016_stats[z3_2016_stats$school == team,],
z4_2016_stats[z4_2016_stats$school == team,])
Setup Comparisons by Down
# ND Pass and Rush Offense and Defense
nd_by_down_2016$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2016$down_pass_num <- paste(nd_by_down_2016$down, ": ", nd_by_down_2016$pass_n, sep = "")
nd_by_down_2016$down_rush_num <- paste(nd_by_down_2016$down, ": ", nd_by_down_2016$rush_n, sep = "")
nd_by_down_2016$down_pass_num_def <- paste(nd_by_down_2016$down, ": ", nd_by_down_2016$pass_n_def, sep = "")
nd_by_down_2016$down_rush_num_def <- paste(nd_by_down_2016$down, ": ", nd_by_down_2016$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_down_2016$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2016$down_pass_num <- paste(mich_by_down_2016$down, ": ", mich_by_down_2016$pass_n, sep = "")
mich_by_down_2016$down_rush_num <- paste(mich_by_down_2016$down, ": ", mich_by_down_2016$rush_n, sep = "")
mich_by_down_2016$down_pass_num_def <- paste(mich_by_down_2016$down, ": ", mich_by_down_2016$pass_n_def, sep = "")
mich_by_down_2016$down_rush_num_def <- paste(mich_by_down_2016$down, ": ", mich_by_down_2016$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2016_down <- cbind.data.frame(nd_by_down_2016$down,
((nd_by_down_2016$pass_wpa_per_play + mich_by_down_2016$pass_wpa_per_play_def)/2),
((nd_by_down_2016$pass_epa_per_play + mich_by_down_2016$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2016_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2016_down <- cbind.data.frame(nd_by_down_2016$down,
((nd_by_down_2016$rush_wpa_per_play + mich_by_down_2016$rush_wpa_per_play_def)/2),
((nd_by_down_2016$rush_epa_per_play + mich_by_down_2016$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2016_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2016_down <- cbind.data.frame(mich_by_down_2016$down,
((mich_by_down_2016$pass_wpa_per_play + nd_by_down_2016$pass_wpa_per_play_def)/2),
((mich_by_down_2016$pass_epa_per_play + nd_by_down_2016$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2016_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2016_down <- cbind.data.frame(mich_by_down_2016$down,
((mich_by_down_2016$rush_wpa_per_play + nd_by_down_2016$rush_wpa_per_play_def)/2),
((mich_by_down_2016$rush_epa_per_play + nd_by_down_2016$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2016_down) <- c("down", "exp_wpa", "exp_epa")
WPA Graphs by Down
# ND Passing Offense vs Michigan Passing Defense
d_2016_1 <- ggplot(data = nd_v_mich_pass_2016_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2016_1
# ND Rushing Offense vs Michigan Rushing Defense
d_2016_2 <- ggplot(data = nd_v_mich_rush_2016_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2016_2
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2016_3 <- ggplot(data = mich_v_nd_pass_2016_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2016_3
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2016_4 <- ggplot(data = mich_v_nd_rush_2016_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2016_4
When analyzing WPA by down, ND should pass on first down, then run on
other downs. Michigan still has a clear advantage over ND on
offense.
EPA by Down
# ND Passing Offense vs Michigan Passing Defense
d_2016_5 <- ggplot(data = nd_v_mich_pass_2016_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2016_5
# ND Rushing Offense vs Michigan Rushing Defense
d_2016_6 <- ggplot(data = nd_v_mich_rush_2016_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2016_6
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2016_7 <- ggplot(data = mich_v_nd_pass_2016_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2016_7
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2016_8 <- ggplot(data = mich_v_nd_rush_2016_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2016 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2016_8
EPA by down paints a scenario where Notre dame should prioritize the ground attack, while Michigan should pass, although they still hold a sizable advantage over the Irish.
Load 2017 Season
# Load data
cfb_2017 <- load_cfb_pbp(seasons = 2017)
# Convert to data frame
cfb_2017 <- as.data.frame(cfb_2017)
Play by Play
# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2017)
# Add summarized play type back to dataset
cfb_2017$playtype <- s_play_type
2017 Notre Dame Offensive Stats
nd_off_2017 <- cfb_2017[which(cfb_2017$pos_team == "Notre Dame" &
cfb_2017$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_off_2017_m <- melt(nd_off_2017[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2017 ND Offense EPA
# Create plot
ggplot(nd_off_2017_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2017 Notre Dame Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.843
# Turn off dark mode
invert_geom_defaults()
2017 Notre Dame Defense
nd_def_2017 <- cfb_2017[which(cfb_2017$def_pos_team == "Notre Dame" &
cfb_2017$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_def_2017_m <- melt(nd_def_2017[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2017 ND Defense EPA
# Create plot
ggplot(nd_def_2017_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2017 Notre Dame Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.745
# Turn off dark mode
invert_geom_defaults()
By Field Position
y1_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$yards_to_goal >= 80,],
team_logos = team_logos)
# Calculate statistics for 20 - 50
y2_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$yards_to_goal >= 50 &
cfb_2017$yards_to_goal < 80,],
team_logos = team_logos)
# Calculate statistics for 50 - 79
y3_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$yards_to_goal >= 21 &
cfb_2017$yards_to_goal < 50,],
team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$yards_to_goal >= 0 &
cfb_2017$yards_to_goal < 20,],
team_logos = team_logos)
2017 Notre Dame
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_yards_2017 <- rbind.data.frame(y1_2017_stats[y1_2017_stats$school == team,],
y2_2017_stats[y2_2017_stats$school == team,],
y3_2017_stats[y3_2017_stats$school == team,],
y4_2017_stats[y4_2017_stats$school == team,])
# Create plot for offensive EPA
nd_epa_off_2017 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2017, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2017
# Create plot for offensive yards
nd_yards_2017 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2017, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2017
2017 Defensive Stats
# Create plot for defensive EPA
nd_epa_def_2017 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2017, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2017
# Yards
# Create plot for defensive yards
nd_yards_2017_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2017, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2017_def
2017 Michigan Offensive Stats
mich_off_2017 <- cfb_2017[which(cfb_2017$pos_team == "Michigan" &
cfb_2017$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_off_2017_m <- melt(mich_off_2017[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2017 Michigan Offense EPA
# Create plot
ggplot(mich_off_2017_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2017 Michigan Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.499
## Warning: Removed 1 rows containing non-finite values (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
2017 Michigan Defense
mich_def_2017 <- cfb_2017[which(cfb_2017$def_pos_team == "Michigan" &
cfb_2017$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_def_2017_m <- melt(mich_def_2017[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2017 Michigan Defense EPA
# Create plot
ggplot(mich_def_2017_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2017 Michigan Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.583
## Warning: Removed 5 rows containing non-finite values (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
By Field Position
2017 Michigan Offense
# Set team as Notre Dame
team <- "Michigan"
# Extract Notre Dame statistics
mich_by_yards_2017 <- rbind.data.frame(y1_2017_stats[y1_2017_stats$school == team,],
y2_2017_stats[y2_2017_stats$school == team,],
y3_2017_stats[y3_2017_stats$school == team,],
y4_2017_stats[y4_2017_stats$school == team,])
# Create plot for offensive EPA
mich_epa_off_2017 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2017, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2017
# Create plot for offensive yards
mich_yards_2017 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2017, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2017
2017 Michigan Defensive Stats
# Create plot for defensive EPA
mich_epa_def_2017 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2017, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2017
# Yards
# Create plot for defensive yards
mich_yards_2017_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2017, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2017_def
# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2017$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2017$zone_pass_num <- paste(nd_by_yards_2017$zone, ": ", nd_by_yards_2017$pass_n, sep = "")
nd_by_yards_2017$zone_rush_num <- paste(nd_by_yards_2017$zone, ": ", nd_by_yards_2017$rush_n, sep = "")
nd_by_yards_2017$zone_pass_num_def <- paste(nd_by_yards_2017$zone, ": ", nd_by_yards_2017$pass_n_def, sep = "")
nd_by_yards_2017$zone_rush_num_def <- paste(nd_by_yards_2017$zone, ": ", nd_by_yards_2017$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_yards_2017$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2017$zone_pass_num <- paste(mich_by_yards_2017$zone, ": ", mich_by_yards_2017$pass_n, sep = "")
mich_by_yards_2017$zone_rush_num <- paste(mich_by_yards_2017$zone, ": ", mich_by_yards_2017$rush_n, sep = "")
mich_by_yards_2017$zone_pass_num_def <- paste(mich_by_yards_2017$zone, ": ", mich_by_yards_2017$pass_n_def, sep = "")
mich_by_yards_2017$zone_rush_num_def <- paste(mich_by_yards_2017$zone, ": ", mich_by_yards_2017$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2017 <- cbind.data.frame(nd_by_yards_2017$zone,
((nd_by_yards_2017$pass_wpa_per_play + mich_by_yards_2017$pass_wpa_per_play_def)/2),
((nd_by_yards_2017$pass_epa_per_play + mich_by_yards_2017$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2017) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2017 <- cbind.data.frame(nd_by_yards_2017$zone,
((nd_by_yards_2017$rush_wpa_per_play + mich_by_yards_2017$rush_wpa_per_play_def)/2),
((nd_by_yards_2017$rush_epa_per_play + mich_by_yards_2017$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2017) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2017 <- cbind.data.frame(mich_by_yards_2017$zone,
((mich_by_yards_2017$pass_wpa_per_play + nd_by_yards_2017$pass_wpa_per_play_def)/2),
((mich_by_yards_2017$pass_epa_per_play + nd_by_yards_2017$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2017) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2017 <- cbind.data.frame(mich_by_yards_2017$zone,
((mich_by_yards_2017$rush_wpa_per_play + nd_by_yards_2017$rush_wpa_per_play_def)/2),
((mich_by_yards_2017$rush_epa_per_play + nd_by_yards_2017$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2017) <- c("zone", "exp_wpa", "exp_epa")
WPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2017_1 <- ggplot(data = nd_v_mich_pass_2017, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2017_1
# ND Rushing Offense vs Michigan Rushing Defense
g_2017_2 <- ggplot(data = nd_v_mich_rush_2017, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2017_2
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2017_3 <- ggplot(data = mich_v_nd_pass_2017, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2017_3
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2017_4 <- ggplot(data = mich_v_nd_rush_2017, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2017_4
According to WPA by field position, this would be an incredibly balanced
matchup. Generally, Notre Dame will have slightly more success passing
the ball while Michigan will be slightly more successful at running the
ball.
EPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2017_5 <- ggplot(data = nd_v_mich_pass_2017, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2017_5
# ND Rushing Offense vs Michigan Rushing Defense
g_2017_6 <- ggplot(data = nd_v_mich_rush_2017, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2017_6
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2017_7 <- ggplot(data = mich_v_nd_pass_2017, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2017_7
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2017_8 <- ggplot(data = mich_v_nd_rush_2017, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2017_8
EPA by field position once again shows how close this matchup would be.
This gives Notre Dame a more balanced approach on offense, but slightly
favors the run. Michigan’s also advocates a balanced attack, but
slightly favors the pass here.
Extracting Plays by Downs
z1_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$down == 1,],
team_logos = team_logos)
# Calculate statistics for 2nd Down
z2_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$down == 2,],
team_logos = team_logos)
# Calculate statistics for 3rd Down
z3_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$down == 3,],
team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2017_stats <- create_team_summary(pbp = cfb_2017[cfb_2017$down == 4,],
team_logos = team_logos)
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_down_2017 <- rbind.data.frame(z1_2017_stats[z1_2017_stats$school == team,],
z2_2017_stats[z2_2017_stats$school == team,],
z3_2017_stats[z3_2017_stats$school == team,],
z4_2017_stats[z4_2017_stats$school == team,])
# Set team as Michigan
team <- "Michigan"
# Extract Michigan statistics
mich_by_down_2017 <- rbind.data.frame(z1_2017_stats[z1_2017_stats$school == team,],
z2_2017_stats[z2_2017_stats$school == team,],
z3_2017_stats[z3_2017_stats$school == team,],
z4_2017_stats[z4_2017_stats$school == team,])
Setup Comparisons by Down
# ND Pass and Rush Offense and Defense
nd_by_down_2017$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2017$down_pass_num <- paste(nd_by_down_2017$down, ": ", nd_by_down_2017$pass_n, sep = "")
nd_by_down_2017$down_rush_num <- paste(nd_by_down_2017$down, ": ", nd_by_down_2017$rush_n, sep = "")
nd_by_down_2017$down_pass_num_def <- paste(nd_by_down_2017$down, ": ", nd_by_down_2017$pass_n_def, sep = "")
nd_by_down_2017$down_rush_num_def <- paste(nd_by_down_2017$down, ": ", nd_by_down_2017$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_down_2017$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2017$down_pass_num <- paste(mich_by_down_2017$down, ": ", mich_by_down_2017$pass_n, sep = "")
mich_by_down_2017$down_rush_num <- paste(mich_by_down_2017$down, ": ", mich_by_down_2017$rush_n, sep = "")
mich_by_down_2017$down_pass_num_def <- paste(mich_by_down_2017$down, ": ", mich_by_down_2017$pass_n_def, sep = "")
mich_by_down_2017$down_rush_num_def <- paste(mich_by_down_2017$down, ": ", mich_by_down_2017$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2017_down <- cbind.data.frame(nd_by_down_2017$down,
((nd_by_down_2017$pass_wpa_per_play + mich_by_down_2017$pass_wpa_per_play_def)/2),
((nd_by_down_2017$pass_epa_per_play + mich_by_down_2017$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2017_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2017_down <- cbind.data.frame(nd_by_down_2017$down,
((nd_by_down_2017$rush_wpa_per_play + mich_by_down_2017$rush_wpa_per_play_def)/2),
((nd_by_down_2017$rush_epa_per_play + mich_by_down_2017$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2017_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2017_down <- cbind.data.frame(mich_by_down_2017$down,
((mich_by_down_2017$pass_wpa_per_play + nd_by_down_2017$pass_wpa_per_play_def)/2),
((mich_by_down_2017$pass_epa_per_play + nd_by_down_2017$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2017_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2017_down <- cbind.data.frame(mich_by_down_2017$down,
((mich_by_down_2017$rush_wpa_per_play + nd_by_down_2017$rush_wpa_per_play_def)/2),
((mich_by_down_2017$rush_epa_per_play + nd_by_down_2017$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2017_down) <- c("down", "exp_wpa", "exp_epa")
WPA Graphs by Down
# ND Passing Offense vs Michigan Passing Defense
d_2017_1 <- ggplot(data = nd_v_mich_pass_2017_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2017_1
# ND Rushing Offense vs Michigan Rushing Defense
d_2017_2 <- ggplot(data = nd_v_mich_rush_2017_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2017_2
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2017_3 <- ggplot(data = mich_v_nd_pass_2017_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2017_3
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2017_4 <- ggplot(data = mich_v_nd_rush_2017_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2017_4
These series of graphs show that Notre Dame is likely going to be more
successful on the ground, while Michigan will be much better suited to
pass the ball except on 3rd down.
EPA by Down
# ND Passing Offense vs Michigan Passing Defense
d_2017_5 <- ggplot(data = nd_v_mich_pass_2017_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2017_5
# ND Rushing Offense vs Michigan Rushing Defense
d_2017_6 <- ggplot(data = nd_v_mich_rush_2017_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2017_6
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2017_7 <- ggplot(data = mich_v_nd_pass_2017_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2017_7
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2017_8 <- ggplot(data = mich_v_nd_rush_2017_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2017 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2017_8
This has similar conclusions, Notre Dame is likely to be more successful
with the rushing attack, Michigan will be more successful throwing the
ball. Michigan’s passing attack has the best matchup.
Load 2020 Season
# Load data
cfb_2020 <- load_cfb_pbp(seasons = 2020)
# Convert to data frame
cfb_2020 <- as.data.frame(cfb_2020)
Play by Play
# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2020)
# Add summarized play type back to dataset
cfb_2020$playtype <- s_play_type
2020 Notre Dame Offensive Stats
nd_off_2020 <- cfb_2020[which(cfb_2020$pos_team == "Notre Dame" &
cfb_2020$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_off_2020_m <- melt(nd_off_2020[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2020 ND Offense EPA
# Create plot
ggplot(nd_off_2020_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2020 Notre Dame Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.499
## Warning: Removed 11 rows containing non-finite values
## (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
2020 Notre Dame Defense
nd_def_2020 <- cfb_2020[which(cfb_2020$def_pos_team == "Notre Dame" &
cfb_2020$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_def_2020_m <- melt(nd_def_2020[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2020 ND Defense EPA
# Create plot
ggplot(nd_def_2020_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2020 Notre Dame Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.767
## Warning: Removed 5 rows containing non-finite values (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
By Field Position
y1_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$yards_to_goal >= 80,],
team_logos = team_logos)
# Calculate statistics for 20 - 50
y2_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$yards_to_goal >= 50 &
cfb_2020$yards_to_goal < 80,],
team_logos = team_logos)
# Calculate statistics for 50 - 79
y3_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$yards_to_goal >= 21 &
cfb_2020$yards_to_goal < 50,],
team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$yards_to_goal >= 0 &
cfb_2020$yards_to_goal < 20,],
team_logos = team_logos)
2020 Notre Dame Offense
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_yards_2020 <- rbind.data.frame(y1_2020_stats[y1_2020_stats$school == team,],
y2_2020_stats[y2_2020_stats$school == team,],
y3_2020_stats[y3_2020_stats$school == team,],
y4_2020_stats[y4_2020_stats$school == team,])
# Create plot for offensive EPA
nd_epa_off_2020 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2020, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2020
# Create plot for offensive yards
nd_yards_2020 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2020, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2020
2020 Defensive Stats
# Create plot for defensive EPA
nd_epa_def_2020 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2020, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2020
# Yards
# Create plot for defensive yards
nd_yards_2020_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2020, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2020_def
2020 Michigan Offensive Stats
mich_off_2020 <- cfb_2020[which(cfb_2020$pos_team == "Michigan" &
cfb_2020$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_off_2020_m <- melt(mich_off_2020[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2020 Michigan Offense EPA
# Create plot
ggplot(mich_off_2020_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2020 Michigan Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.947
## Warning: Removed 15 rows containing non-finite values
## (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
2020 Michigan Defense
mich_def_2020 <- cfb_2020[which(cfb_2020$def_pos_team == "Michigan" &
cfb_2020$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_def_2020_m <- melt(mich_def_2020[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2020 Michigan Defense EPA
# Create plot
ggplot(mich_def_2020_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2020 Michigan Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.795
## Warning: Removed 7 rows containing non-finite values (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
By Field Position
2020 Michigan Offense
# Set team as Notre Dame
team <- "Michigan"
# Extract Notre Dame statistics
mich_by_yards_2020 <- rbind.data.frame(y1_2020_stats[y1_2020_stats$school == team,],
y2_2020_stats[y2_2020_stats$school == team,],
y3_2020_stats[y3_2020_stats$school == team,],
y4_2020_stats[y4_2020_stats$school == team,])
# Create plot for offensive EPA
mich_epa_off_2020 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2020, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2020
# Create plot for offensive yards
mich_yards_2020 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2020, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2020
2020 Michigan Defensive Stats
# Create plot for defensive EPA
mich_epa_def_2020 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2020, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2020
# Yards
# Create plot for defensive yards
mich_yards_2020_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2020, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2020_def
# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2020$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2020$zone_pass_num <- paste(nd_by_yards_2020$zone, ": ", nd_by_yards_2020$pass_n, sep = "")
nd_by_yards_2020$zone_rush_num <- paste(nd_by_yards_2020$zone, ": ", nd_by_yards_2020$rush_n, sep = "")
nd_by_yards_2020$zone_pass_num_def <- paste(nd_by_yards_2020$zone, ": ", nd_by_yards_2020$pass_n_def, sep = "")
nd_by_yards_2020$zone_rush_num_def <- paste(nd_by_yards_2020$zone, ": ", nd_by_yards_2020$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_yards_2020$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2020$zone_pass_num <- paste(mich_by_yards_2020$zone, ": ", mich_by_yards_2020$pass_n, sep = "")
mich_by_yards_2020$zone_rush_num <- paste(mich_by_yards_2020$zone, ": ", mich_by_yards_2020$rush_n, sep = "")
mich_by_yards_2020$zone_pass_num_def <- paste(mich_by_yards_2020$zone, ": ", mich_by_yards_2020$pass_n_def, sep = "")
mich_by_yards_2020$zone_rush_num_def <- paste(mich_by_yards_2020$zone, ": ", mich_by_yards_2020$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2020 <- cbind.data.frame(nd_by_yards_2020$zone,
((nd_by_yards_2020$pass_wpa_per_play + mich_by_yards_2020$pass_wpa_per_play_def)/2),
((nd_by_yards_2020$pass_epa_per_play + mich_by_yards_2020$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2020) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2020 <- cbind.data.frame(nd_by_yards_2020$zone,
((nd_by_yards_2020$rush_wpa_per_play + mich_by_yards_2020$rush_wpa_per_play_def)/2),
((nd_by_yards_2020$rush_epa_per_play + mich_by_yards_2020$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2020) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2020 <- cbind.data.frame(mich_by_yards_2020$zone,
((mich_by_yards_2020$pass_wpa_per_play + nd_by_yards_2020$pass_wpa_per_play_def)/2),
((mich_by_yards_2020$pass_epa_per_play + nd_by_yards_2020$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2020) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2020 <- cbind.data.frame(mich_by_yards_2020$zone,
((mich_by_yards_2020$rush_wpa_per_play + nd_by_yards_2020$rush_wpa_per_play_def)/2),
((mich_by_yards_2020$rush_epa_per_play + nd_by_yards_2020$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2020) <- c("zone", "exp_wpa", "exp_epa")
WPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2020_1 <- ggplot(data = nd_v_mich_pass_2020, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2020_1
# ND Rushing Offense vs Michigan Rushing Defense
g_2020_2 <- ggplot(data = nd_v_mich_rush_2020, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2020_2
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2020_3 <- ggplot(data = mich_v_nd_pass_2020, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2020_3
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2020_4 <- ggplot(data = mich_v_nd_rush_2020, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2020_4
According to WPA by field position, Notre Dame’s offense generally has
far more favorable matchups against Michigan than the other way around.
ND’s passing offense against Michigan’s passing defense serves as the
best individual matchup.
EPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2020_5 <- ggplot(data = nd_v_mich_pass_2020, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2020_5
# ND Rushing Offense vs Michigan Rushing Defense
g_2020_6 <- ggplot(data = nd_v_mich_rush_2020, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2020_6
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2020_7 <- ggplot(data = mich_v_nd_pass_2020, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2020_7
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2020_8 <- ggplot(data = mich_v_nd_rush_2020, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2020_8
Similar situation to previous graph, Notre Dame would fare incredibly
well throwing the ball and fairly well running the ball, while Michigan
would throw the ball okay but struggle running the ball.
Extracting Plays by Downs
z1_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$down == 1,],
team_logos = team_logos)
# Calculate statistics for 2nd Down
z2_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$down == 2,],
team_logos = team_logos)
# Calculate statistics for 3rd Down
z3_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$down == 3,],
team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2020_stats <- create_team_summary(pbp = cfb_2020[cfb_2020$down == 4,],
team_logos = team_logos)
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_down_2020 <- rbind.data.frame(z1_2020_stats[z1_2020_stats$school == team,],
z2_2020_stats[z2_2020_stats$school == team,],
z3_2020_stats[z3_2020_stats$school == team,],
z4_2020_stats[z4_2020_stats$school == team,])
# Set team as Michigan
team <- "Michigan"
# Extract Michigan statistics
mich_by_down_2020 <- rbind.data.frame(z1_2020_stats[z1_2020_stats$school == team,],
z2_2020_stats[z2_2020_stats$school == team,],
z3_2020_stats[z3_2020_stats$school == team,],
z4_2020_stats[z4_2020_stats$school == team,])
Setup Comparisons by Down
# ND Pass and Rush Offense and Defense
nd_by_down_2020$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2020$down_pass_num <- paste(nd_by_down_2020$down, ": ", nd_by_down_2020$pass_n, sep = "")
nd_by_down_2020$down_rush_num <- paste(nd_by_down_2020$down, ": ", nd_by_down_2020$rush_n, sep = "")
nd_by_down_2020$down_pass_num_def <- paste(nd_by_down_2020$down, ": ", nd_by_down_2020$pass_n_def, sep = "")
nd_by_down_2020$down_rush_num_def <- paste(nd_by_down_2020$down, ": ", nd_by_down_2020$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_down_2020$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2020$down_pass_num <- paste(mich_by_down_2020$down, ": ", mich_by_down_2020$pass_n, sep = "")
mich_by_down_2020$down_rush_num <- paste(mich_by_down_2020$down, ": ", mich_by_down_2020$rush_n, sep = "")
mich_by_down_2020$down_pass_num_def <- paste(mich_by_down_2020$down, ": ", mich_by_down_2020$pass_n_def, sep = "")
mich_by_down_2020$down_rush_num_def <- paste(mich_by_down_2020$down, ": ", mich_by_down_2020$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2020_down <- cbind.data.frame(nd_by_down_2020$down,
((nd_by_down_2020$pass_wpa_per_play + mich_by_down_2020$pass_wpa_per_play_def)/2),
((nd_by_down_2020$pass_epa_per_play + mich_by_down_2020$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2020_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2020_down <- cbind.data.frame(nd_by_down_2020$down,
((nd_by_down_2020$rush_wpa_per_play + mich_by_down_2020$rush_wpa_per_play_def)/2),
((nd_by_down_2020$rush_epa_per_play + mich_by_down_2020$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2020_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2020_down <- cbind.data.frame(mich_by_down_2020$down,
((mich_by_down_2020$pass_wpa_per_play + nd_by_down_2020$pass_wpa_per_play_def)/2),
((mich_by_down_2020$pass_epa_per_play + nd_by_down_2020$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2020_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2020_down <- cbind.data.frame(mich_by_down_2020$down,
((mich_by_down_2020$rush_wpa_per_play + nd_by_down_2020$rush_wpa_per_play_def)/2),
((mich_by_down_2020$rush_epa_per_play + nd_by_down_2020$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2020_down) <- c("down", "exp_wpa", "exp_epa")
WPA Graphs by Down
# ND Passing Offense vs Michigan Passing Defense
d_2020_1 <- ggplot(data = nd_v_mich_pass_2020_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2020_1
# ND Rushing Offense vs Michigan Rushing Defense
d_2020_2 <- ggplot(data = nd_v_mich_rush_2020_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2020_2
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2020_3 <- ggplot(data = mich_v_nd_pass_2020_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2020_3
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2020_4 <- ggplot(data = mich_v_nd_rush_2020_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2020_4
Notre Dame’s passing offense is the best individual matchup here. Both
Michigan and ND would pass it fine, but Notre Dame wpuld dominate on the
ground.
EPA by Down
# ND Passing Offense vs Michigan Passing Defense
d_2020_5 <- ggplot(data = nd_v_mich_pass_2020_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2020_5
# ND Rushing Offense vs Michigan Rushing Defense
d_2020_6 <- ggplot(data = nd_v_mich_rush_2020_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2020_6
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2020_7 <- ggplot(data = mich_v_nd_pass_2020_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2020_7
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2020_8 <- ggplot(data = mich_v_nd_rush_2020_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2020 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2020_8
Similar situation, Notre Dame passing offense and favorable rushing offense beats Michigan’s passing offense and unfavorable rushing offense.
Load 2021 Season
# Load data
cfb_2021 <- load_cfb_pbp(seasons = 2021)
# Convert to data frame
cfb_2021 <- as.data.frame(cfb_2021)
Play by Play
# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2021)
# Add summarized play type back to dataset
cfb_2021$playtype <- s_play_type
2021 Notre Dame Offensive Stats
nd_off_2021 <- cfb_2021[which(cfb_2021$pos_team == "Notre Dame" &
cfb_2021$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_off_2021_m <- melt(nd_off_2021[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2020 ND Offense EPA
# Create plot
ggplot(nd_off_2021_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2021 Notre Dame Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.854
## Warning: Removed 3 rows containing non-finite values (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
2021 Notre Dame Defense
nd_def_2021 <- cfb_2021[which(cfb_2021$def_pos_team == "Notre Dame" &
cfb_2021$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_def_2021_m <- melt(nd_def_2021[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2021 ND Defense EPA
# Create plot
ggplot(nd_def_2021_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2021 Notre Dame Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.61
## Warning: Removed 3 rows containing non-finite values (`stat_density_ridges()`).
# Turn off dark mode
invert_geom_defaults()
By Field Position
y1_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$yards_to_goal >= 80,],
team_logos = team_logos)
# Calculate statistics for 20 - 50
y2_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$yards_to_goal >= 50 &
cfb_2021$yards_to_goal < 80,],
team_logos = team_logos)
# Calculate statistics for 50 - 79
y3_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$yards_to_goal >= 21 &
cfb_2021$yards_to_goal < 50,],
team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$yards_to_goal >= 0 &
cfb_2021$yards_to_goal < 20,],
team_logos = team_logos)
2021 Notre Dame Offense
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_yards_2021 <- rbind.data.frame(y1_2021_stats[y1_2021_stats$school == team,],
y2_2021_stats[y2_2021_stats$school == team,],
y3_2021_stats[y3_2021_stats$school == team,],
y4_2021_stats[y4_2021_stats$school == team,])
# Create plot for offensive EPA
nd_epa_off_2021 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2021, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2021
# Create plot for offensive yards
nd_yards_2021 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2021, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2021
2021 Defensive Stats
# Create plot for defensive EPA
nd_epa_def_2021 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2021, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2021
# Yards
# Create plot for defensive yards
nd_yards_2021_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2021, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2021_def
2021 Michigan Offensive Stats
mich_off_2021 <- cfb_2021[which(cfb_2021$pos_team == "Michigan" &
cfb_2021$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_off_2021_m <- melt(mich_off_2021[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2021 Michigan Offense EPA
# Create plot
ggplot(mich_off_2021_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2021 Michigan Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.547
# Turn off dark mode
invert_geom_defaults()
2021 Michigan Defense
mich_def_2021 <- cfb_2021[which(cfb_2021$def_pos_team == "Michigan" &
cfb_2021$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_def_2021_m <- melt(mich_def_2021[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2021 Michigan Defense EPA
# Create plot
ggplot(mich_def_2021_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2021 Michigan Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.7
# Turn off dark mode
invert_geom_defaults()
By Field Position
2021 Michigan Offense
# Set team as Notre Dame
team <- "Michigan"
# Extract Notre Dame statistics
mich_by_yards_2021 <- rbind.data.frame(y1_2021_stats[y1_2021_stats$school == team,],
y2_2021_stats[y2_2021_stats$school == team,],
y3_2021_stats[y3_2021_stats$school == team,],
y4_2021_stats[y4_2021_stats$school == team,])
# Create plot for offensive EPA
mich_epa_off_2021 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2021, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2021
# Create plot for offensive yards
mich_yards_2021 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2021, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2021
2021 Michigan Defensive Stats
# Create plot for defensive EPA
mich_epa_def_2021 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2021, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2021
# Yards
# Create plot for defensive yards
mich_yards_2021_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2021, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2021_def
# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2021$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2021$zone_pass_num <- paste(nd_by_yards_2021$zone, ": ", nd_by_yards_2021$pass_n, sep = "")
nd_by_yards_2021$zone_rush_num <- paste(nd_by_yards_2021$zone, ": ", nd_by_yards_2021$rush_n, sep = "")
nd_by_yards_2021$zone_pass_num_def <- paste(nd_by_yards_2021$zone, ": ", nd_by_yards_2021$pass_n_def, sep = "")
nd_by_yards_2021$zone_rush_num_def <- paste(nd_by_yards_2021$zone, ": ", nd_by_yards_2021$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_yards_2021$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2021$zone_pass_num <- paste(mich_by_yards_2021$zone, ": ", mich_by_yards_2021$pass_n, sep = "")
mich_by_yards_2021$zone_rush_num <- paste(mich_by_yards_2021$zone, ": ", mich_by_yards_2021$rush_n, sep = "")
mich_by_yards_2021$zone_pass_num_def <- paste(mich_by_yards_2021$zone, ": ", mich_by_yards_2021$pass_n_def, sep = "")
mich_by_yards_2021$zone_rush_num_def <- paste(mich_by_yards_2021$zone, ": ", mich_by_yards_2021$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2021 <- cbind.data.frame(nd_by_yards_2021$zone,
((nd_by_yards_2021$pass_wpa_per_play + mich_by_yards_2021$pass_wpa_per_play_def)/2),
((nd_by_yards_2021$pass_epa_per_play + mich_by_yards_2021$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2021) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2021 <- cbind.data.frame(nd_by_yards_2021$zone,
((nd_by_yards_2021$rush_wpa_per_play + mich_by_yards_2021$rush_wpa_per_play_def)/2),
((nd_by_yards_2021$rush_epa_per_play + mich_by_yards_2021$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2021) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2021 <- cbind.data.frame(mich_by_yards_2021$zone,
((mich_by_yards_2021$pass_wpa_per_play + nd_by_yards_2021$pass_wpa_per_play_def)/2),
((mich_by_yards_2021$pass_epa_per_play + nd_by_yards_2021$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2021) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2021 <- cbind.data.frame(mich_by_yards_2021$zone,
((mich_by_yards_2021$rush_wpa_per_play + nd_by_yards_2021$rush_wpa_per_play_def)/2),
((mich_by_yards_2021$rush_epa_per_play + nd_by_yards_2021$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2021) <- c("zone", "exp_wpa", "exp_epa")
WPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2021_1 <- ggplot(data = nd_v_mich_pass_2021, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2021_1
# ND Rushing Offense vs Michigan Rushing Defense
g_2021_2 <- ggplot(data = nd_v_mich_rush_2021, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2021_2
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2021_3 <- ggplot(data = mich_v_nd_pass_2021, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2021_3
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2021_4 <- ggplot(data = mich_v_nd_rush_2021, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2021_4
Michigan with an easy advantage here. Notre Dame passing offense is
better, but rushing offense is non existent. Michigan offense in general
can move on ND’s defense.
EPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2021_5 <- ggplot(data = nd_v_mich_pass_2021, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2021_5
# ND Rushing Offense vs Michigan Rushing Defense
g_2021_6 <- ggplot(data = nd_v_mich_rush_2021, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2021_6
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2021_7 <- ggplot(data = mich_v_nd_pass_2021, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2021_7
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2021_8 <- ggplot(data = mich_v_nd_rush_2021, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2021_8
Paints a better picture for the Irish passing attack, but they would
still be outclassed on the ground. Michigan rushing offense looking
generally better compared to their passing attack, but a balanced
approach would be encouraged.
Extracting Plays by Downs
z1_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$down == 1,],
team_logos = team_logos)
# Calculate statistics for 2nd Down
z2_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$down == 2,],
team_logos = team_logos)
# Calculate statistics for 3rd Down
z3_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$down == 3,],
team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2021_stats <- create_team_summary(pbp = cfb_2021[cfb_2021$down == 4,],
team_logos = team_logos)
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_down_2021 <- rbind.data.frame(z1_2021_stats[z1_2021_stats$school == team,],
z2_2021_stats[z2_2021_stats$school == team,],
z3_2021_stats[z3_2021_stats$school == team,],
z4_2021_stats[z4_2021_stats$school == team,])
# Set team as Michigan
team <- "Michigan"
# Extract Michigan statistics
mich_by_down_2021 <- rbind.data.frame(z1_2021_stats[z1_2021_stats$school == team,],
z2_2021_stats[z2_2021_stats$school == team,],
z3_2021_stats[z3_2021_stats$school == team,],
z4_2021_stats[z4_2021_stats$school == team,])
Setup Comparisons by Down
# ND Pass and Rush Offense and Defense
nd_by_down_2021$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2021$down_pass_num <- paste(nd_by_down_2021$down, ": ", nd_by_down_2021$pass_n, sep = "")
nd_by_down_2021$down_rush_num <- paste(nd_by_down_2021$down, ": ", nd_by_down_2021$rush_n, sep = "")
nd_by_down_2021$down_pass_num_def <- paste(nd_by_down_2021$down, ": ", nd_by_down_2021$pass_n_def, sep = "")
nd_by_down_2021$down_rush_num_def <- paste(nd_by_down_2021$down, ": ", nd_by_down_2021$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_down_2021$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2021$down_pass_num <- paste(mich_by_down_2021$down, ": ", mich_by_down_2021$pass_n, sep = "")
mich_by_down_2021$down_rush_num <- paste(mich_by_down_2021$down, ": ", mich_by_down_2021$rush_n, sep = "")
mich_by_down_2021$down_pass_num_def <- paste(mich_by_down_2021$down, ": ", mich_by_down_2021$pass_n_def, sep = "")
mich_by_down_2021$down_rush_num_def <- paste(mich_by_down_2021$down, ": ", mich_by_down_2021$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2021_down <- cbind.data.frame(nd_by_down_2021$down,
((nd_by_down_2021$pass_wpa_per_play + mich_by_down_2021$pass_wpa_per_play_def)/2),
((nd_by_down_2021$pass_epa_per_play + mich_by_down_2021$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2021_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2021_down <- cbind.data.frame(nd_by_down_2021$down,
((nd_by_down_2021$rush_wpa_per_play + mich_by_down_2021$rush_wpa_per_play_def)/2),
((nd_by_down_2021$rush_epa_per_play + mich_by_down_2021$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2021_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2021_down <- cbind.data.frame(mich_by_down_2021$down,
((mich_by_down_2021$pass_wpa_per_play + nd_by_down_2021$pass_wpa_per_play_def)/2),
((mich_by_down_2021$pass_epa_per_play + nd_by_down_2021$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2021_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2021_down <- cbind.data.frame(mich_by_down_2021$down,
((mich_by_down_2021$rush_wpa_per_play + nd_by_down_2021$rush_wpa_per_play_def)/2),
((mich_by_down_2021$rush_epa_per_play + nd_by_down_2021$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2021_down) <- c("down", "exp_wpa", "exp_epa")
WPA Graphs by Down
# ND Passing Offense vs Michigan Passing Defense
d_2021_1 <- ggplot(data = nd_v_mich_pass_2021_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2021_1
# ND Rushing Offense vs Michigan Rushing Defense
d_2021_2 <- ggplot(data = nd_v_mich_rush_2021_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2021_2
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2021_3 <- ggplot(data = mich_v_nd_pass_2021_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2021_3
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2021_4 <- ggplot(data = mich_v_nd_rush_2021_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2021_4
Offenses have an advantage, but Michigan’s is performing better in both
the pass and rush categories.
EPA by Down
# ND Passing Offense vs Michigan Passing Defense
d_2021_5 <- ggplot(data = nd_v_mich_pass_2021_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2021_5
# ND Rushing Offense vs Michigan Rushing Defense
d_2021_6 <- ggplot(data = nd_v_mich_rush_2021_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2021_6
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2021_7 <- ggplot(data = mich_v_nd_pass_2021_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2021_7
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2021_8 <- ggplot(data = mich_v_nd_rush_2021_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2021 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2021_8
Notre Dame would only fare decently by passing the ball, Michigan would be best taking a balanced approach.
Load 2022 Season
# Load data
cfb_2022 <- load_cfb_pbp(seasons = 2022)
# Convert to data frame
cfb_2022 <- as.data.frame(cfb_2022)
Play by Play
# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2022)
# Add summarized play type back to dataset
cfb_2022$playtype <- s_play_type
2022 Notre Dame Offensive Stats
nd_off_2022 <- cfb_2022[which(cfb_2022$pos_team == "Notre Dame" &
cfb_2022$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_off_2022_m <- melt(nd_off_2022[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2022 ND Offense EPA
# Create plot
ggplot(nd_off_2022_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2022 Notre Dame Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.856
# Turn off dark mode
invert_geom_defaults()
2022 Notre Dame Defense
nd_def_2022 <- cfb_2022[which(cfb_2022$def_pos_team == "Notre Dame" &
cfb_2022$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_def_2022_m <- melt(nd_def_2022[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2022 ND Defense EPA
# Create plot
ggplot(nd_def_2022_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2022 Notre Dame Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.8
# Turn off dark mode
invert_geom_defaults()
By Field Position
y1_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$yards_to_goal >= 80,],
team_logos = team_logos)
# Calculate statistics for 20 - 50
y2_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$yards_to_goal >= 50 &
cfb_2022$yards_to_goal < 80,],
team_logos = team_logos)
# Calculate statistics for 50 - 79
y3_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$yards_to_goal >= 21 &
cfb_2022$yards_to_goal < 50,],
team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$yards_to_goal >= 0 &
cfb_2022$yards_to_goal < 20,],
team_logos = team_logos)
2022 Notre Dame Offense
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_yards_2022 <- rbind.data.frame(y1_2022_stats[y1_2022_stats$school == team,],
y2_2022_stats[y2_2022_stats$school == team,],
y3_2022_stats[y3_2022_stats$school == team,],
y4_2022_stats[y4_2022_stats$school == team,])
# Create plot for offensive EPA
nd_epa_off_2022 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2022, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2022
# Create plot for offensive yards
nd_yards_2022 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2022, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2022
2022 Defensive Stats
# Create plot for defensive EPA
nd_epa_def_2022 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2022, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2022
# Yards
# Create plot for defensive yards
nd_yards_2022_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2022, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2022_def
2022 Michigan Offensive Stats
mich_off_2022 <- cfb_2022[which(cfb_2022$pos_team == "Michigan" &
cfb_2022$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_off_2022_m <- melt(mich_off_2022[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2022 Michigan Offense EPA
# Create plot
ggplot(mich_off_2022_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2022 Michigan Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.556
# Turn off dark mode
invert_geom_defaults()
2022 Michigan Defense
mich_def_2022 <- cfb_2022[which(cfb_2022$def_pos_team == "Michigan" &
cfb_2022$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_def_2022_m <- melt(mich_def_2022[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2022 Michigan Defense EPA
# Create plot
ggplot(mich_def_2022_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2022 Michigan Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.698
# Turn off dark mode
invert_geom_defaults()
By Field Position
2022 Michigan Offense
# Set team as Notre Dame
team <- "Michigan"
# Extract Notre Dame statistics
mich_by_yards_2022 <- rbind.data.frame(y1_2022_stats[y1_2022_stats$school == team,],
y2_2022_stats[y2_2022_stats$school == team,],
y3_2022_stats[y3_2022_stats$school == team,],
y4_2022_stats[y4_2022_stats$school == team,])
# Create plot for offensive EPA
mich_epa_off_2022 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2022, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2022
# Create plot for offensive yards
mich_yards_2022 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2022, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2022
2022 Michigan Defensive Stats
# Create plot for defensive EPA
mich_epa_def_2022 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2022, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2022
# Yards
# Create plot for defensive yards
mich_yards_2022_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2022, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2022_def
# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2022$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2022$zone_pass_num <- paste(nd_by_yards_2022$zone, ": ", nd_by_yards_2022$pass_n, sep = "")
nd_by_yards_2022$zone_rush_num <- paste(nd_by_yards_2022$zone, ": ", nd_by_yards_2022$rush_n, sep = "")
nd_by_yards_2022$zone_pass_num_def <- paste(nd_by_yards_2022$zone, ": ", nd_by_yards_2022$pass_n_def, sep = "")
nd_by_yards_2022$zone_rush_num_def <- paste(nd_by_yards_2022$zone, ": ", nd_by_yards_2022$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_yards_2022$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2022$zone_pass_num <- paste(mich_by_yards_2022$zone, ": ", mich_by_yards_2022$pass_n, sep = "")
mich_by_yards_2022$zone_rush_num <- paste(mich_by_yards_2022$zone, ": ", mich_by_yards_2022$rush_n, sep = "")
mich_by_yards_2022$zone_pass_num_def <- paste(mich_by_yards_2022$zone, ": ", mich_by_yards_2022$pass_n_def, sep = "")
mich_by_yards_2022$zone_rush_num_def <- paste(mich_by_yards_2022$zone, ": ", mich_by_yards_2022$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2022 <- cbind.data.frame(nd_by_yards_2022$zone,
((nd_by_yards_2022$pass_wpa_per_play + mich_by_yards_2022$pass_wpa_per_play_def)/2),
((nd_by_yards_2022$pass_epa_per_play + mich_by_yards_2022$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2022) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2022 <- cbind.data.frame(nd_by_yards_2022$zone,
((nd_by_yards_2022$rush_wpa_per_play + mich_by_yards_2022$rush_wpa_per_play_def)/2),
((nd_by_yards_2022$rush_epa_per_play + mich_by_yards_2022$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2022) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2022 <- cbind.data.frame(mich_by_yards_2022$zone,
((mich_by_yards_2022$pass_wpa_per_play + nd_by_yards_2022$pass_wpa_per_play_def)/2),
((mich_by_yards_2022$pass_epa_per_play + nd_by_yards_2022$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2022) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2022 <- cbind.data.frame(mich_by_yards_2022$zone,
((mich_by_yards_2022$rush_wpa_per_play + nd_by_yards_2022$rush_wpa_per_play_def)/2),
((mich_by_yards_2022$rush_epa_per_play + nd_by_yards_2022$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2022) <- c("zone", "exp_wpa", "exp_epa")
WPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2022_1 <- ggplot(data = nd_v_mich_pass_2022, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2022_1
# ND Rushing Offense vs Michigan Rushing Defense
g_2022_2 <- ggplot(data = nd_v_mich_rush_2022, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2022_2
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2022_3 <- ggplot(data = mich_v_nd_pass_2022, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2022_3
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2022_4 <- ggplot(data = mich_v_nd_rush_2022, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2022_4
Notre Dame would once again be better served to go with the pass.
Michigan would as well; Wolverines generally have an edge across the
board.
EPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2022_5 <- ggplot(data = nd_v_mich_pass_2022, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2022_5
# ND Rushing Offense vs Michigan Rushing Defense
g_2022_6 <- ggplot(data = nd_v_mich_rush_2022, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2022_6
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2022_7 <- ggplot(data = mich_v_nd_pass_2022, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2022_7
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2022_8 <- ggplot(data = mich_v_nd_rush_2022, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2022_8
Notre Dame would be better to pass the ball, while Michigan would use a
more balanced attack.
Extracting Plays by Downs
z1_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$down == 1,],
team_logos = team_logos)
# Calculate statistics for 2nd Down
z2_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$down == 2,],
team_logos = team_logos)
# Calculate statistics for 3rd Down
z3_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$down == 3,],
team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2022_stats <- create_team_summary(pbp = cfb_2022[cfb_2022$down == 4,],
team_logos = team_logos)
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_down_2022 <- rbind.data.frame(z1_2022_stats[z1_2022_stats$school == team,],
z2_2022_stats[z2_2022_stats$school == team,],
z3_2022_stats[z3_2022_stats$school == team,],
z4_2022_stats[z4_2022_stats$school == team,])
# Set team as Michigan
team <- "Michigan"
# Extract Michigan statistics
mich_by_down_2022 <- rbind.data.frame(z1_2022_stats[z1_2022_stats$school == team,],
z2_2022_stats[z2_2022_stats$school == team,],
z3_2022_stats[z3_2022_stats$school == team,],
z4_2022_stats[z4_2022_stats$school == team,])
Setup Comparisons by Down
# ND Pass and Rush Offense and Defense
nd_by_down_2022$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2022$down_pass_num <- paste(nd_by_down_2022$down, ": ", nd_by_down_2022$pass_n, sep = "")
nd_by_down_2022$down_rush_num <- paste(nd_by_down_2022$down, ": ", nd_by_down_2022$rush_n, sep = "")
nd_by_down_2022$down_pass_num_def <- paste(nd_by_down_2022$down, ": ", nd_by_down_2022$pass_n_def, sep = "")
nd_by_down_2022$down_rush_num_def <- paste(nd_by_down_2022$down, ": ", nd_by_down_2022$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_down_2022$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2022$down_pass_num <- paste(mich_by_down_2022$down, ": ", mich_by_down_2022$pass_n, sep = "")
mich_by_down_2022$down_rush_num <- paste(mich_by_down_2022$down, ": ", mich_by_down_2022$rush_n, sep = "")
mich_by_down_2022$down_pass_num_def <- paste(mich_by_down_2022$down, ": ", mich_by_down_2022$pass_n_def, sep = "")
mich_by_down_2022$down_rush_num_def <- paste(mich_by_down_2022$down, ": ", mich_by_down_2022$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2022_down <- cbind.data.frame(nd_by_down_2022$down,
((nd_by_down_2022$pass_wpa_per_play + mich_by_down_2022$pass_wpa_per_play_def)/2),
((nd_by_down_2022$pass_epa_per_play + mich_by_down_2022$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2022_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2022_down <- cbind.data.frame(nd_by_down_2022$down,
((nd_by_down_2022$rush_wpa_per_play + mich_by_down_2022$rush_wpa_per_play_def)/2),
((nd_by_down_2022$rush_epa_per_play + mich_by_down_2022$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2022_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2022_down <- cbind.data.frame(mich_by_down_2022$down,
((mich_by_down_2022$pass_wpa_per_play + nd_by_down_2022$pass_wpa_per_play_def)/2),
((mich_by_down_2022$pass_epa_per_play + nd_by_down_2022$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2022_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2022_down <- cbind.data.frame(mich_by_down_2022$down,
((mich_by_down_2022$rush_wpa_per_play + nd_by_down_2022$rush_wpa_per_play_def)/2),
((mich_by_down_2022$rush_epa_per_play + nd_by_down_2022$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2022_down) <- c("down", "exp_wpa", "exp_epa")
WPA Graphs by Down
# ND Passing Offense vs Michigan Passing Defense
d_2022_1 <- ggplot(data = nd_v_mich_pass_2022_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2022_1
# ND Rushing Offense vs Michigan Rushing Defense
d_2022_2 <- ggplot(data = nd_v_mich_rush_2022_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2022_2
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2022_3 <- ggplot(data = mich_v_nd_pass_2022_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2022_3
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2022_4 <- ggplot(data = mich_v_nd_rush_2022_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2022_4
Irish - pass. Wolverines - balanced attack.
EPA by Down
# ND Passing Offense vs Michigan Passing Defense
d_2022_5 <- ggplot(data = nd_v_mich_pass_2022_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2022_5
# ND Rushing Offense vs Michigan Rushing Defense
d_2022_6 <- ggplot(data = nd_v_mich_rush_2022_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2022_6
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2022_7 <- ggplot(data = mich_v_nd_pass_2022_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2022_7
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2022_8 <- ggplot(data = mich_v_nd_rush_2022_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2022 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2022_8
Same as other analyses, but gives more favorability to Michigan’s passing attack.
Load 2023 Season
# Load data
cfb_2023 <- load_cfb_pbp(seasons = 2023)
# Convert to data frame
cfb_2023 <- as.data.frame(cfb_2023)
Play by Play
# Create summarized play type
s_play_type <- create_simplex_play_type(cfb_2023)
# Add summarized play type back to dataset
cfb_2023$playtype <- s_play_type
2023 Notre Dame Offensive Stats
nd_off_2023 <- cfb_2023[which(cfb_2023$pos_team == "Notre Dame" &
cfb_2023$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_off_2023_m <- melt(nd_off_2023[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2023 ND Offense EPA
# Create plot
ggplot(nd_off_2023_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2023 Notre Dame Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.795
# Turn off dark mode
invert_geom_defaults()
2023 Notre Dame Defense
nd_def_2023 <- cfb_2023[which(cfb_2023$def_pos_team == "Notre Dame" &
cfb_2023$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
nd_def_2023_m <- melt(nd_def_2023[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2023 ND Defense EPA
# Create plot
ggplot(nd_def_2023_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2023 Notre Dame Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.625
# Turn off dark mode
invert_geom_defaults()
By Field Position
y1_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$yards_to_goal >= 80,],
team_logos = team_logos)
# Calculate statistics for 20 - 50
y2_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$yards_to_goal >= 50 &
cfb_2023$yards_to_goal < 80,],
team_logos = team_logos)
# Calculate statistics for 50 - 79
y3_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$yards_to_goal >= 21 &
cfb_2023$yards_to_goal < 50,],
team_logos = team_logos)
# Calculate statistics for 80 - 100
y4_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$yards_to_goal >= 0 &
cfb_2023$yards_to_goal < 20,],
team_logos = team_logos)
2023 Notre Dame Offense
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_yards_2023 <- rbind.data.frame(y1_2023_stats[y1_2023_stats$school == team,],
y2_2023_stats[y2_2023_stats$school == team,],
y3_2023_stats[y3_2023_stats$school == team,],
y4_2023_stats[y4_2023_stats$school == team,])
# Create plot for offensive EPA
nd_epa_off_2023 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2023, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
nd_epa_off_2023
# Create plot for offensive yards
nd_yards_2023 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2023, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
nd_yards_2023
2023 Defensive Stats
# Create plot for defensive EPA
nd_epa_def_2023 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2023, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
nd_epa_def_2023
# Yards
# Create plot for defensive yards
nd_yards_2023_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Notre Dame", # Set Team
res_1 = nd_by_yards_2023, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
nd_yards_2023_def
2023 Michigan Offensive Stats
mich_off_2023 <- cfb_2023[which(cfb_2023$pos_team == "Michigan" &
cfb_2023$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_off_2023_m <- melt(mich_off_2023[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2023 Michigan Offense EPA
# Create plot
ggplot(mich_off_2023_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set geom density ridges
theme_minimal() + # Set theme as minimal
labs(x = "EPA", title = "EPA by Play Type and Down", # Set labels for plot
subtitle = "2023 Michigan Offense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw()+ # Convert to dark theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.468
# Turn off dark mode
invert_geom_defaults()
2023 Michigan Defense
mich_def_2023 <- cfb_2023[which(cfb_2023$def_pos_team == "Michigan" &
cfb_2023$playtype %in% c("Rush", "Pass")),
c("down", "playtype", "wpa", "EPA")]
mich_def_2023_m <- melt(mich_def_2023[,c("down", "playtype", "EPA")], id.vars = c("down", "playtype"))
2023 Michigan Defense EPA
# Create plot
ggplot(mich_def_2023_m, # Set dataset
aes(x = value, y = factor(down), fill = factor(playtype)))+ # Set aesthetics
geom_density_ridges(alpha = 0.5) + # Set density to create distribution
theme_minimal() + # Set plot theme
labs(x = "EPA", title = "EPA by Play Type and Down", # Set plot labels
subtitle = "2023 Michigan Defense",
fill = "Play Type", y = "Down") +
scale_fill_manual(values = c("Pass" = "red", "Rush" = "Blue")) + # Set colors manually
dark_theme_bw() + # Set theme
geom_vline(xintercept = 0, linetype = 2, color = "white") # Add vertical line at 0
## Inverted geom defaults of fill and color/colour.
## To change them back, use invert_geom_defaults().
## Picking joint bandwidth of 0.637
# Turn off dark mode
invert_geom_defaults()
By Field Position
2023 Michigan Offense
# Set team as Notre Dame
team <- "Michigan"
# Extract Notre Dame statistics
mich_by_yards_2023 <- rbind.data.frame(y1_2023_stats[y1_2023_stats$school == team,],
y2_2023_stats[y2_2023_stats$school == team,],
y3_2023_stats[y3_2023_stats$school == team,],
y4_2023_stats[y4_2023_stats$school == team,])
# Create plot for offensive EPA
mich_epa_off_2023 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2023, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "off") # Set type of plot to generate
# Generate plot for offensive EPA
mich_epa_off_2023
# Create plot for offensive yards
mich_yards_2023 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2023, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "off") # Set type of plot to generate
# Generate plot
mich_yards_2023
2023 Michigan Defensive Stats
# Create plot for defensive EPA
mich_epa_def_2023 <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2023, # Set Team Stats
type = "EPA", # Set type as EPA
off_def = "def") # Set type of plot to generate
# Generate plot for defensive EPA
mich_epa_def_2023
# Yards
# Create plot for defensive yards
mich_yards_2023_def <- create_zone_plot(logos = team_logos, # Set team logos
t1 = "Michigan", # Set Team
res_1 = mich_by_yards_2023, # Set Team Stats
type = "Yards", # Set type as Yards
off_def = "def") # Set type of plot to generate
# Generate plot
mich_yards_2023_def
# Setting Data Up
# ND Pass and Rush Offense and Defense
nd_by_yards_2023$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
nd_by_yards_2023$zone_pass_num <- paste(nd_by_yards_2023$zone, ": ", nd_by_yards_2023$pass_n, sep = "")
nd_by_yards_2023$zone_rush_num <- paste(nd_by_yards_2023$zone, ": ", nd_by_yards_2023$rush_n, sep = "")
nd_by_yards_2023$zone_pass_num_def <- paste(nd_by_yards_2023$zone, ": ", nd_by_yards_2023$pass_n_def, sep = "")
nd_by_yards_2023$zone_rush_num_def <- paste(nd_by_yards_2023$zone, ": ", nd_by_yards_2023$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_yards_2023$zone <- c("Own 20", "Own 20-50", "Opponent 20-50", "Opponent 20")
mich_by_yards_2023$zone_pass_num <- paste(mich_by_yards_2023$zone, ": ", mich_by_yards_2023$pass_n, sep = "")
mich_by_yards_2023$zone_rush_num <- paste(mich_by_yards_2023$zone, ": ", mich_by_yards_2023$rush_n, sep = "")
mich_by_yards_2023$zone_pass_num_def <- paste(mich_by_yards_2023$zone, ": ", mich_by_yards_2023$pass_n_def, sep = "")
mich_by_yards_2023$zone_rush_num_def <- paste(mich_by_yards_2023$zone, ": ", mich_by_yards_2023$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2023 <- cbind.data.frame(nd_by_yards_2023$zone,
((nd_by_yards_2023$pass_wpa_per_play + mich_by_yards_2023$pass_wpa_per_play_def)/2),
((nd_by_yards_2023$pass_epa_per_play + mich_by_yards_2023$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2023) <- c("zone", "exp_wpa", "exp_epa")
nd_v_mich_rush_2023 <- cbind.data.frame(nd_by_yards_2023$zone,
((nd_by_yards_2023$rush_wpa_per_play + mich_by_yards_2023$rush_wpa_per_play_def)/2),
((nd_by_yards_2023$rush_epa_per_play + mich_by_yards_2023$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2023) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_pass_2023 <- cbind.data.frame(mich_by_yards_2023$zone,
((mich_by_yards_2023$pass_wpa_per_play + nd_by_yards_2023$pass_wpa_per_play_def)/2),
((mich_by_yards_2023$pass_epa_per_play + nd_by_yards_2023$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2023) <- c("zone", "exp_wpa", "exp_epa")
mich_v_nd_rush_2023 <- cbind.data.frame(mich_by_yards_2023$zone,
((mich_by_yards_2023$rush_wpa_per_play + nd_by_yards_2023$rush_wpa_per_play_def)/2),
((mich_by_yards_2023$rush_epa_per_play + nd_by_yards_2023$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2023) <- c("zone", "exp_wpa", "exp_epa")
WPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2023_1 <- ggplot(data = nd_v_mich_pass_2023, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2023_1
# ND Rushing Offense vs Michigan Rushing Defense
g_2023_2 <- ggplot(data = nd_v_mich_rush_2023, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2023_2
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2023_3 <- ggplot(data = mich_v_nd_pass_2023, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2023_3
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2023_4 <- ggplot(data = mich_v_nd_rush_2023, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2023_4
Both teams would be much better served to throw the ball primarily.
EPA Graphs by Field Position
# ND Passing Offense vs Michigan Passing Defense
g_2023_5 <- ggplot(data = nd_v_mich_pass_2023, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2023_5
# ND Rushing Offense vs Michigan Rushing Defense
g_2023_6 <- ggplot(data = nd_v_mich_rush_2023, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2023_6
# Michigan Pass Offense vs Notre Dame Pass Defense
g_2023_7 <- ggplot(data = mich_v_nd_pass_2023, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Win Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2023_7
# Michigan Rush Offense vs Notre Dame Rush Defense
g_2023_8 <- ggplot(data = mich_v_nd_rush_2023, # Set dataset
aes(x = zone, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = zone)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Field Zones", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
g_2023_8
Both teams would be much better positioned to throw the ball compared to running it.
Extracting Plays by Downs
z1_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$down == 1,],
team_logos = team_logos)
# Calculate statistics for 2nd Down
z2_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$down == 2,],
team_logos = team_logos)
# Calculate statistics for 3rd Down
z3_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$down == 3,],
team_logos = team_logos)
# Calculate statistics for 4th Down
z4_2023_stats <- create_team_summary(pbp = cfb_2023[cfb_2023$down == 4,],
team_logos = team_logos)
# Set team as Notre Dame
team <- "Notre Dame"
# Extract Notre Dame statistics
nd_by_down_2023 <- rbind.data.frame(z1_2023_stats[z1_2023_stats$school == team,],
z2_2023_stats[z2_2023_stats$school == team,],
z3_2023_stats[z3_2023_stats$school == team,],
z4_2023_stats[z4_2023_stats$school == team,])
# Set team as Michigan
team <- "Michigan"
# Extract Michigan statistics
mich_by_down_2023 <- rbind.data.frame(z1_2023_stats[z1_2023_stats$school == team,],
z2_2023_stats[z2_2023_stats$school == team,],
z3_2023_stats[z3_2023_stats$school == team,],
z4_2023_stats[z4_2023_stats$school == team,])
Setup Comparisons by Down
# ND Pass and Rush Offense and Defense
nd_by_down_2023$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
nd_by_down_2023$down_pass_num <- paste(nd_by_down_2023$down, ": ", nd_by_down_2023$pass_n, sep = "")
nd_by_down_2023$down_rush_num <- paste(nd_by_down_2023$down, ": ", nd_by_down_2023$rush_n, sep = "")
nd_by_down_2023$down_pass_num_def <- paste(nd_by_down_2023$down, ": ", nd_by_down_2023$pass_n_def, sep = "")
nd_by_down_2023$down_rush_num_def <- paste(nd_by_down_2023$down, ": ", nd_by_down_2023$rush_n_def, sep = "")
# Michigan Pass and Rush Offense and Defense
mich_by_down_2023$down <- c("1st Down", "2nd Down", "3rd Down", "4th Down")
mich_by_down_2023$down_pass_num <- paste(mich_by_down_2023$down, ": ", mich_by_down_2023$pass_n, sep = "")
mich_by_down_2023$down_rush_num <- paste(mich_by_down_2023$down, ": ", mich_by_down_2023$rush_n, sep = "")
mich_by_down_2023$down_pass_num_def <- paste(mich_by_down_2023$down, ": ", mich_by_down_2023$pass_n_def, sep = "")
mich_by_down_2023$down_rush_num_def <- paste(mich_by_down_2023$down, ": ", mich_by_down_2023$rush_n_def, sep = "")
# Create Different Combinations
nd_v_mich_pass_2023_down <- cbind.data.frame(nd_by_down_2023$down,
((nd_by_down_2023$pass_wpa_per_play + mich_by_down_2023$pass_wpa_per_play_def)/2),
((nd_by_down_2023$pass_epa_per_play + mich_by_down_2023$pass_epa_per_play_def)/2))
names(nd_v_mich_pass_2023_down) <- c("down", "exp_wpa", "exp_epa")
nd_v_mich_rush_2023_down <- cbind.data.frame(nd_by_down_2023$down,
((nd_by_down_2023$rush_wpa_per_play + mich_by_down_2023$rush_wpa_per_play_def)/2),
((nd_by_down_2023$rush_epa_per_play + mich_by_down_2023$rush_epa_per_play_def)/2))
names(nd_v_mich_rush_2023_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_pass_2023_down <- cbind.data.frame(mich_by_down_2023$down,
((mich_by_down_2023$pass_wpa_per_play + nd_by_down_2023$pass_wpa_per_play_def)/2),
((mich_by_down_2023$pass_epa_per_play + nd_by_down_2023$pass_epa_per_play_def)/2))
names(mich_v_nd_pass_2023_down) <- c("down", "exp_wpa", "exp_epa")
mich_v_nd_rush_2023_down <- cbind.data.frame(mich_by_down_2023$down,
((mich_by_down_2023$rush_wpa_per_play + nd_by_down_2023$rush_wpa_per_play_def)/2),
((mich_by_down_2023$rush_epa_per_play + nd_by_down_2023$rush_epa_per_play_def)/2))
names(mich_v_nd_rush_2023_down) <- c("down", "exp_wpa", "exp_epa")
WPA Graphs by Down
# ND Passing Offense vs Michigan Passing Defense
d_2023_1 <- ggplot(data = nd_v_mich_pass_2023_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2023_1
# ND Rushing Offense vs Michigan Rushing Defense
d_2023_2 <- ggplot(data = nd_v_mich_rush_2023_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Win Probability Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2023_2
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2023_3 <- ggplot(data = mich_v_nd_pass_2023_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2023_3
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2023_4 <- ggplot(data = mich_v_nd_rush_2023_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_wpa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Win Probability Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2023_4
Both teams would be better served to throw the ball compared to running it.
EPA by Down
# ND Passing Offense vs Michigan Passing Defense
d_2023_5 <- ggplot(data = nd_v_mich_pass_2023_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Pass",
title = "Notre Dame Passing Offense vs Michigan Pass Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2023_5
# ND Rushing Offense vs Michigan Rushing Defense
d_2023_6 <- ggplot(data = nd_v_mich_rush_2023_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Notre Dame's Expected Points Against Michigan Whenever They Rush",
title = "Notre Dame Rush Offense vs Michigan Rush Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2023_6
# Michigan Pass Offense vs Notre Dame Pass Defense
d_2023_7 <- ggplot(data = mich_v_nd_pass_2023_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Pass",
title = "Michigan Passing Offense vs Notre Dame Pass Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2023_7
# Michigan Rush Offense vs Notre Dame Rush Defense
d_2023_8 <- ggplot(data = mich_v_nd_rush_2023_down, # Set dataset
aes(x = down, # Set x axis as pitch type
y= exp_epa, # Set y as win probability change
fill = down)) + # Set fill as pitch type
geom_bar(stat = "identity") + # Set geom bar for bar plot
theme_bw() + # Set theme
coord_flip() + # Flip coordinates
labs(x = "Down", # Set labels
y = "Average Change in Michigan's Expected Points Against Notre Dame Whenever They Rush",
title = "Michigan Rush Offense vs Notre Dame Rush Defense",
subtitle = "2023 Season") +
theme(panel.grid.major = element_blank(), # Remove grid
panel.grid.minor = element_blank(), # Remove grid
panel.border = element_blank(), # Remove grid
panel.background = element_blank()) + # Remove grid
guides(fill = NULL) # Turn off legend for fill
d_2023_8
Both teams would be much better served to throw the ball compared to running it.